Я заставил ИИ‑агента работать по протоколу тушения пожаров. За шесть дней он переписал сам себя 10 раз. LLM-агенты.. LLM-агенты. искусственный интеллект.. LLM-агенты. искусственный интеллект. Управление разработкой.

В какой‑то момент я перестал доверять фразе «готово» от языковой модели.

Модель при этом не врёт: она честно считает, что сделала работу. Она пишет «реализовано, тесты проходят», и чаще всего так и есть. Беда в остальных случаях, когда модель поправила один вызов из четырёх, приняла собственный план за выполненную работу и закрыла задачу. Диалог при этом выглядит безупречно до самого конца.

Я перепробовал обычный набор: писал развесистые CLAUDE.md, требовал план перед кодом, просил самопроверку. Всё это держалось ровно до тех пор, пока не становилось неудобным. Правило, написанное словами, агент нарушает в тот момент, когда у него появляется разумно выглядящая причина его обойти, и такая причина находится всегда.

Искать готовую систему управления мне далеко не пришлось. До ухода ExxonMobil из России я работал там аналитиком по аварийно‑спасательному реагированию, и ICS была в этой роли повседневным рабочим инструментом: я прошёл по ней множество тренингов, включая уровень Advanced, и работал фасилитатором IMT, Incident Management Team. Оттуда и пришла мысль перенести ICS в разработку.

ICS: система, придуманная после пожаров 1970 года

С 22 сентября по 4 октября 1970 года Южная Калифорния горела тринадцать дней подряд. 773 пожара, 576 508 акров (около 233 тысяч гектаров), 722 сгоревших дома, 16 погибших. Разбор показал занятную вещь: ресурсов хватало, не хватало управления. Пожарные части округов, федеральная лесная служба, полиция и муниципалы работали одновременно на одном пожаре, с разными позывными, разными структурами подчинения, разными формами отчётности и без единого командира.

Из этого разбора вырос FIRESCOPE (FIrefighting REsources of Southern California Organized for Potential Emergencies): конгресс выделил лесной службе США 900 тысяч долларов на систему, которая позволила бы разным ведомствам работать на одном пожаре согласованно. К 1972 году внутри проекта родилась Incident Command System, ICS. В 2003-м директива HSPD-5 обязала все федеральные ведомства США принять NIMS, чьей основой ICS и является, а с 2005 финансового года это стало условием получения федеральных денег на готовность к чрезвычайным ситуациям.

Устроена она так. Командует первый прибывший на место, по одной простой причине: кто‑то должен командовать с первой минуты, и опытность тут вторична. Когда появляется квалифицированный командир, происходит transfer of command, явная и произносимая вслух процедура передачи. То, что первый прибывший может оказаться самым зелёным в округе, заложено в конструкцию. Из всего ICS именно эту процедуру я считал самой протокольной и наименее полезной, пока не начал раскладывать по ролям языковые модели.

Разворачивается система ровно настолько, насколько требует масштаб. На возгорании мусорного бака командир совмещает в себе все должности; на пожаре в шесть дивизионов появляются секции планирования, операций, логистики и финансов. Живёт инцидент оперативными периодами, обычно по смене в 12 часов, и на каждый период пишется IAP, Incident Action Plan: цели периода, тактика, распределение ресурсов, карта. Следующая смена принимает работу по бумаге, устный рассказ силы не имеет.

Цикл, который производит этот план, называется Planning P — по форме схемы, которой его рисуют во всех учебниках ICS. Ножка буквы P — то, что случается один раз, в начале инцидента: первое донесение, разведка обстановки, начальный брифинг. Кольцо — то, что повторяется каждый оперативный период: оценить обстановку → поставить цели периода → выработать тактику → собрать IAP → утвердить его → довести до людей → исполнять → оценить результат → войти в следующий период. По этой петле инцидент ходит до самого закрытия, и дальше в статье слова «период», «цели», «тактика» и «план» означают ровно эти узлы.

Planning P

Planning P

Бумага пронумерована. ICS 201 — начальный брифинг. ICS 202 — цели периода. ICS 204 — назначение конкретной группе: кто, что, на каком участке, кому докладывает. ICS 214 — журнал, куда пишут всё подряд в хронологии. По моему опыту, 214 — самая скучная из четырёх форм и единственная, без которой потом ничего не восстановить.

Территория поделена: каждой группе назначен свой участок фронта, чтобы две бригады не делали одну работу дважды и не мешали друг другу. А над всем этим сидит офицер по безопасности, отдельная должность вне секций, подчинённая напрямую командиру, с правом немедленно остановить любую работу. Его нельзя переспорить бригадиром, только командиром, и то письменно.

Всё это я знал наизусть задолго до всякого Claude Code, а сходство разглядел позже. Оркестрация ИИ‑агентов страдает ровно тем же, чем страдала Калифорния в 1970-м: исполнителей достаточно, дисциплины разделения труда нет, журнала не ведёт никто.

Почему ICS ложится на агентов лучше, чем офисные методологии

У Scrum, канбана и прочего есть встроенное допущение: участники присутствуют одновременно. Стендап работает, потому что все собрались в одной комнате и слушают. У субагентов языковой модели этого нет вообще: субагент рождается с пустым контекстом, делает один заход и умирает. В Claude Code он вдобавок не может породить собственного субагента, вложенность запрещена.

ICS оказался почти единственной известной мне системой управления, спроектированной изначально под «связи может не быть». Пожарный расчёт в дыму не звонит начальнику секции планирования, он выполняет свой ICS 204 и пишет в журнал.

Отсюда два решения, на которых стоит вся конструкция. Первое: иерархию задают фазы, вложенности нет. Начальники секций планируют (фаза A), командир утверждает, специалисты исполняют (фаза B), офицер по безопасности проверяет (фаза C). Никто никем не руководит в реальном времени: начальник смены передаёт дела ночному через журнал. Цепочка команд превращается во временнóй конвейер, и каждый переход оставляет файл на диске.

Второе решение оказалось важнее первого: оперативный период равен контекстному окну. Контекст гниет, файлы нет. Инцидент в DCS живёт в каталоге .dcs/incidents/2026-07-28-<слаг>/ и состоит из тех же форм: 201-BRIEF.md202-OBJECTIVES.md204-TASKING-*.mdIAP.md214-LOG.mdSAFETY.mdAAR.md. Любая сессия, любая модель, в любой день открывает каталог и продолжает с того места, где всё остановилось. Полный сброс контекста перестаёт быть катастрофой и становится сменой караула.

Что получилось: DCS

DCS, Development Command System, ставится как пакет для Claude Code:

npm i -g dcs-command-system

Дальше в проекте: /dcs-init один раз, потом /dcs-run <описание бага или фичи> на каждую задачу.

Соответствие вышло почти дословным:

ICS

DCS

Инцидент

Единица работы: баг, фича, находка аудита

Тип инцидента (5/3/1)

Тип задачи: тривиальная / нормальная / архитектурная

Planning P

Цикл: цели → тактика → план → утверждение → исполнение → проверка

IAP

IAP.md, утверждается человеком, хэш прибит к утверждению

ICS 201/202/204/214

Те же файлы с теми же номерами

Transfer of command

Диспетчер работает на любой модели, командные решения уходят на сильнейшую

Разделение территории

Непересекающиеся области файлов на каждого специалиста

Офицер по безопасности

Отдельный агент с правом обязательной остановки

Оперативный период

Одно контекстное окно

Демобилизация

Слияние ветки и деплой‑процедура

Роли разложены по уровням моделей: владелец — человек; командир инцидента — сильнейшая доступная модель; начальники секций и офицер по безопасности — Opus; специалисты‑исполнители — Sonnet, до четырёх штук за период, каждый со своей территорией файлов.

Главная механика: гейт

Всё остальное — обвязка вокруг одного правила: ни одной правки исходников, пока нет утверждённого плана.

Держит его механика: PreToolUse‑хук на Edit/Write физически отказывает в вызове, пока в каталоге инцидента нет файла IAP-APPROVED с совпадающим хэшем.

Хэш прибит к содержимому плана. Отредактировали IAP.md после утверждения — sha256 разъехался, утверждение аннулировано механически, гейт снова закрыт. Никакой переменной окружения для обхода нет. Единственный санкционированный способ снять гейт: человек удаляет .dcs/ACTIVE руками.

Внутри проекта есть табличка, которую я считаю самым честным его куском:

Правило словами

Что произошло

Что реально удержало

«Диспетчер обязан советоваться с командиром»

нарушено 3 раза в первый же день

чек‑лист перед штампом плюс входные гейты, читающие журнал

«Ревизию делает свежий агент; возобновлять прежнего запрещено»

нарушено дважды после того, как правило было внедрено

гейт, запрещающий возобновление сессии с агентом на старом контексте, только полный респаун

«Никакого кода до утверждённого плана»

PreToolUse‑хук, ноль нарушений

Единственное правило без нарушений с самого начала было механизмом. Отсюда вывод, вокруг которого потом собралась вся система: текст агента документирует механизм, но не заменяет его.

Офицер по безопасности (Safety Officer), или почему нельзя верить собственному отчёту

После того как специалисты отработали, порождается отдельный агент, которому в устав записано: опровергать заявление о готовности (подтверждение в задачу не входит); никогда не принимать самоотчёт специалиста как доказательство, только как утверждение, требующее проверки; самому смотреть настоящий git diff и самому запускать тесты. Его остановка обязательна к исполнению, инцидент не закрывается поверх неразрешённого опровержения.

Он ловит ровно тот разрыв, из‑за которого я всё это затеял: между «я комплексно выполнил задачу» и «я сделал какую‑то отдельно стоящую задачу».

Если бы мне разрешили перенести в агентную обвязку ровно одну должность из ICS, я бы взял эту. Ценность её видна не сразу: пока всё идёт нормально, офицер по безопасности выглядит лишним расходом, а окупается он ровно в тот момент, когда все остальные уверены, что работа готова.

Обкатка: пекарня целиком в одном репозитории

DCS с первого дня жил на боевом проекте: это система пекарни на закваске D&K Sourdough, в репозитории она проходит как bread_bot, так как начиналась с простого Телеграм‑бота. Cистема держит на себе работу пекарни целиком, от приёма заказа до расчёта себестоимости нашей продукции.

Лендинг — React‑PWA на TypeScript плюс телеграм‑бот. Под ними FastAPI и SQLite в режиме WAL. Предметная область по состоянию на 28 июля 2026 года: заказы и корзина, оплаты, подписки, склад со списанием ингредиентов по FIFO, рецептуры, планирование выпечки и загрузки печей, себестоимость, маржинальность, финансовый анализ, займы, поставщики, зарплатная аналитика, промоакции и колесо фортуны, отзывы, лист ожидания, вход по телефону с одноразовым кодом и по WebAuthn, push‑уведомления, голосовой помощник. Тридцать четыре роутера API, около сорока моделей, сто пятьдесят пятая версия схемы миграций.

Размер на ту же дату (find … -name '*.py' | xargs wc -l): около 118 тысяч строк Python без учёта тестов и около 108 тысяч строк TypeScript в 312 файлах, при 287 тестовых файлах и 4171 тестовой функции. Всё это крутится на одном сервере с 2 ГБ памяти, откуда отдельное правило: фронтенд собирается локально, потому что vite build на проде ушёл бы в OOM. Сервер живёт по UTC, а пекарня на Сахалине, UTC+11, поэтому в проекте запрещены datetime.now() и date.today() — только обёртки, знающие про часовой пояс.

У проекта есть и собственные жёсткие протоколы, записанные в его же CLAUDE.md: перед нетривиальной правкой прочитать нужный домен базы знаний в Obsidian и записать урок после; перед правкой функции, которую кто‑то вызывает, спросить граф вызовов через MCP; перед разбором любого бага сходить в append‑only таблицу action_log, где лежат все мутации, входы, нажатия в боте и пойманные исключения с трассировками. DCS про всё это ничего не знает и знать не должен: агенты вычитывают протоколы из CLAUDE.md целевого репозитория и соблюдают их внутри своей роли, а сам пакет никаких фактов о пекарне с собой не возит.

Ни одно правило самого DCS тоже не придумано умозрительно: каждый механизм назван по инциденту, который его породил.

Семь первых инцидентов дали такую картину (числа из vault/Metrics/, снимок на 25 июля):

инцидент

брифинг, кБ

журнал, кБ

остановок

эскалаций

analytics‑patterns‑dynamics‑mismatch

9

6

0

0

nan‑guard‑admin‑forms

10

5

0

0

shift‑overview‑rework

7

30

0

0

analytics‑order‑date‑tz‑sweep

11

10

0

0

energy‑cost‑model‑rework

32

285

10

3

migration‑number‑allocation

15

23

0

0

subscription‑refund‑cluster

27

26

0

1

Шесть из семи прошли без единой остановки офицера. Один выбивается из ряда так, что по нему написан отдельный разбор.

31 час, который переписал доктрину

energy-cost-model-rework: 31 час, 54 файла, 11 тысяч строк, 10 остановок офицера, 4 ревизии одного периода, 3 эскалации, журнал на 285 кБ. По дороге инцидент породил собственный блокирующий инцидент.

Самое неприятное открытие разбора в том, что все механизмы отработали правильно. Каждая остановка была по делу. Один из отказов утвердить план поймал проверку, которая обязана была выдать ложное отклонение на исправном деплое: она требовала, чтобы SHA коммита слияния совпал с запечатанным SHA, что при --no-ff невозможно по построению. Порождённый блокирующий инцидент оказался настоящим багом, портившим продакшен: две ветки выделяли себе один и тот же номер миграции, и при слиянии конфликта не возникало.

Стоимость пришла из другого места, из объёма, впущенного на входе и потом ничем не ограниченного. Брифинг открывался словами «модель энергии посчитана неправильно» и дальше перечислял ещё три дефекта, один из которых прямо сейчас портил данные. Все четыре уехали в один инцидент. Типизация была верной и бесполезной: тип задаёт церемонию, но никогда не задавал размер. Из этого выросли три правила.

Один инцидент — один дефект: независимая корневая причина, которая чинится и выкатывается сама по себе. Нашли в брифинге четыре, зарегистрируйте четыре, откройте тот, что на критическом пути.

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

По умолчанию нужно закрыть, слить, выкатить, а остаток зарегистрировать отдельно. Период 1 произвёл проверенное исправление, которое потом лежало в ветке, пока дефект продолжал портить данные в проде. Период 2 существовал только затем, чтобы период 1 стало можно выкатить.

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

Момент, когда система начала чинить себя

23 июля я вынес DCS в отдельный репозиторий, а 25-го поймал себя на том, что правлю доктрину руками, без плана, без проверки и без журнала. Ровно то, что система запрещает делать с любым другим кодом.

С версии 0.6.3 изменения в DCS оформляются как инциденты DCS. Работает это благодаря одной изоляции: C:DCS — канонический репозиторий, инцидент правит его, а ~/.claude/dcs/ — установленная копия, которую читает работающая сессия. Специалист, переписывающий dcs/workflows/plan.md, не может изменить тот plan.md, по которому сессия прямо сейчас работает. Установка — это шаг деплоя, и запускать её во время активного инцидента запрещено жёстким правилом.

Оправдалось это быстрее, чем я рассчитывал.

Файл на 6,3 МБ

Мой package.json дорос до 6,3 МБ, и это заметил не я. В CLAUDE.md было записано правило: писать файлы только инструментами Write/Edit, никогда через PowerShell Set-Content/Out-File, потому что тот добавляет BOM, а BOM уже дважды ломал сравнение хэшей. Тринадцать релизов подряд версия поднималась скриптом на Get-Content -Raw плюс WriteAllText. Формально не запрещённый глагол, ровно та же поломка: PowerShell 5.1 читает в системной кодовой странице, поэтому каждая правка декодировала длинное тире как cp1251 и заново кодировала в UTF-8. Три байта становились шестью, шесть двенадцатью:

коммит

размер package.json

d5d8106

1 378 символов

537177a

4 356

6b72a63

139 473

6a57b97

6 322 630

npm publish упал на 13,5 МБ. Файл всё это время оставался корректным JSON, рост сидел в одном поле, а существующая проверка кодировки искала BOM и U+FFFD, тогда как дважды закодированный текст является корректным UTF-8. Размер файла не измерял ни один из механизмов, которые у меня к тому моменту были.

Выводы отсюда переносятся на любой проект. Правило, сформулированное через глагол, будет исполнено буквально про глагол. Проверка ловит ту поломку, из которой родилась, и слепа к соседней. А у артефакта со стабильным ожидаемым размером должен быть дешёвый инвариант на размер: проверка «package.json меньше 8 кБ» покраснела бы на первом же подъёме версии.

Потолок на остановках

Другой инцидент прокатился по дыре в правилах до десяти остановок за две попытки и шестнадцати часов сорока минут непрерывного исполнения. Восемь остановок из десяти вообще не содержали функционального дефекта: офицер по безопасности каждый раз пересматривал форму, то есть устаревшее число, формулировку, строку документации. К четвёртой итерации я перестал разбирать остановки поштучно и выдал общее «продолжайте» на всё оставшееся. Это и есть тот человеческий отказ, ради которого нужен механический потолок: на десятой остановке «продолжайте» становится рефлексом. Теперь потолок считает сам хук, по опубликованной процедуре журнала, и упирание в него даёт отказ на уровне хука. Ответ владельца «продолжайте» его не сбрасывает.

Планка, сработавшая на своём авторе

Один инцидент шёл под лозунгом «убрать невоспроизводимые числа из артефактов», и его собственный verbose положил такое число в соседний файл. Офицер по безопасности это нашёл, не смог воспроизвести число ни одним запросом, назвал «тем же классом дефекта, ради которого открыт инцидент, воспроизведённым в одном файле сбоку» — и всё равно оставил его рекомендацией, без остановки, потому что ни один критерий приёмки не покрывал этот текст, а останавливать слияние из‑за числа в исторической заметке ровно тот расход, который инцидент открывался прекратить.

Переносимость между моделями

Ради этого пункта я и взял за основу именно ICS. Первый прибывший на место там не обязан быть квалифицированным командиром: он принимает вызов, ведёт начальные действия, а командование переходит квалифицированному, когда тот появляется. В DCS это перенесено буквально.

Модель, на которой запущена сессия, работает диспетчером. Любая: Opus, Sonnet, хоть Haiku. Если сторонняя модель может работать с Anthropic API в Claude Code, то и она (например DeepSeek v4, при условии правильно прописанного фолбэка). Диспетчер порождает агентов, переписывает их ответы в файлы, штампует хэши, ведёт журнал, разговаривает с владельцем.

Командные решения от диспетчера отделены и вынесены в четыре точки: типизация инцидента, приём плана, разбор отклонения, распоряжение по вердикту. В этих точках диспетчер порождает отдельного агента‑командира на сильнейшей доступной модели и передаёт ему только нужные входные данные. Подменять командное решение собственным «чтобы сэкономить один запуск» ему запрещено, потому что это ровно тот срез, на котором в настоящем ICS размывается власть: «да я сам решу, чего дёргать командира».

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

Всё вместе даёт свойство, которого я хотел с самого начала: никакая часть работы не привязана к конкретной модели. Артефакты — обычный markdown, гейт — Python на стандартной библиотеке без зависимостей, состояние — файлы в git. Инцидент, начатый одной моделью, доводится другой; сессия, потерявшая контекст, восстанавливается из каталога; более слабая модель ведёт всю механику, а суждение уходит наверх только там, где оно правда нужно.

Из той же логики выросла параллельность: каждый инцидент получает свой git worktree и ветку, одновременные инциденты держат непересекающиеся территории файлов, слияние стало обязательным шагом закрытия, а деплой идёт с неизменно чистого main отдельным «деплой‑поездом». Аналогия тут прямая, и в доктрине она записана именно так: worktree — это дивизион на линии огня со своей землёй и своей бригадой, основной checkout — площадка сосредоточения, куда работа стягивается после проверки, а деплой‑поезд — демобилизация: формальный и учтённый вывод сил, который никто не делает самовольно.

Побочный эффект, которого я не планировал: доказуемый надзор

Большинство агентных обвязок производит планы. DCS вдобавок производит доказательства того, как решение принималось, причём делает это по ходу работы, без отдельного режима отчётности. Сам я заметил это поздно.

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

Это ровно тот набор, который просят статьи 12 и 14 EU AI Act, ISO/IEC 42 001 и NIST AI RMF: чтобы человеческий надзор над ИИ был демонстрируемым, а решения восстановимыми постфактум. Обычная агентная разработка это уничтожает, потому что её рассуждения живут в чате, который никто не хранит.

Оговорюсь прямо: пакет не ставит перед собой задачу сертифицируемости. Соответствие обеспечивают система менеджмента, аудит и люди. DCS делает только одно: складывает доказательства в репозиторий в момент принятия решения, задолго до того, как кто‑нибудь о нём спросит.

Что стоит знать до установки

Весь этот процесс — не бесплатный. Полный цикл третьего типа — это порождение аналитиков, начальника сектора планирования, до четырёх специалистов и офицера безопасности. Для опечатки это overkill, поэтому есть пятый тип: один специалист, никакой бюрократии, гейта нет вообще. Тип присваивает командир на первой командной точке, и это одно из немногих мест, где ошибка дорога.

Гейт молчит, пока нет активного инцидента. DCS включается по задаче и только по ней. Отдельный необязательный хук на первом сообщении сессии один раз спрашивает, не оформить ли это как инцидент, и явно запускает вопросы, разведку, тривиальные правки и документацию.

Обновление пакета не обновляет проект. Хуки, которые реально что‑то запрещают, лежат внутри проекта в .claude/hooks/ и приезжают только через повторный /dcs-init. Новые ключи .dcs/config.json вообще нужно дописывать руками: шаблон копируется только когда файла нет.

Сегодняшние цифры

Версия 0.6.12, шесть дней от первого коммита. Проверочные наборы: test_doctrine_integrity.py 86 случаев, test_dcs_gate.py 100 случаев, test_dcs_intake.py 10 случаев. Все три гоняются перед каждым слиянием, и красный результат его блокирует. Каждая проверка существует потому, что соответствующий дефект хотя бы однажды уехал в релиз.

Кода в системе почти нет: два хука на стандартной библиотеке Python, ~900 строк вместе. Остальное markdown: доктрина, десять процессов, шесть уставов агентов, шаблоны форм.

  • GitHub: github.com/4evercool/dcs-command-system

  • npm: npm i -g dcs-command-system

  • Лицензия MIT

Чем всё это оказалось

Я начинал с попытки заставить агента не врать про «готово», а закончил тем, что переложил в код систему, по которой когда‑то разбирал учения.

Языковая модель без внешних ограничений ведёт себя как отряд без командира: исполнителей хватает, каждый компетентен, коллективный результат хаотичен. ICS решил эту задачу в 1970-х для людей, которые вообще не могли выбрать себе структуру подчинения, потому что приехали из разных ведомств. Решение перенеслось почти без правок: достаточно заменить «связи может не быть» на «контекста может не быть» и превратить живую иерархию во временнýю.

Единственное, что я добавил от себя и в чём уверен: любое правило, живущее только в тексте промпта, будет нарушено. Причём разумной, добросовестно рассуждающей сессией, у которой была причина и ни один, даже самый правильно составленный Claude.md, этому не будет помехой. Правило начинает работать в тот момент, когда процесс при его невыполнении механически останавливается.

Автор: ZdenniZ

Источник