В какой‑то момент я перестал доверять фразе «готово» от языковой модели.
Модель при этом не врёт: она честно считает, что сделала работу. Она пишет «реализовано, тесты проходят», и чаще всего так и есть. Беда в остальных случаях, когда модель поправила один вызов из четырёх, приняла собственный план за выполненную работу и закрыла задачу. Диалог при этом выглядит безупречно до самого конца.
Я перепробовал обычный набор: писал развесистые 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 → утвердить его → довести до людей → исполнять → оценить результат → войти в следующий период. По этой петле инцидент ходит до самого закрытия, и дальше в статье слова «период», «цели», «тактика» и «план» означают ровно эти узлы.

Бумага пронумерована. 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.md, 202-OBJECTIVES.md, 204-TASKING-*.md, IAP.md, 214-LOG.md, SAFETY.md, AAR.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 |
|
|
ICS 201/202/204/214 |
Те же файлы с теми же номерами |
|
Transfer of command |
Диспетчер работает на любой модели, командные решения уходят на сильнейшую |
|
Разделение территории |
Непересекающиеся области файлов на каждого специалиста |
|
Офицер по безопасности |
Отдельный агент с правом обязательной остановки |
|
Оперативный период |
Одно контекстное окно |
|
Демобилизация |
Слияние ветки и деплой‑процедура |
Роли разложены по уровням моделей: владелец — человек; командир инцидента — сильнейшая доступная модель; начальники секций и офицер по безопасности — Opus; специалисты‑исполнители — Sonnet, до четырёх штук за период, каждый со своей территорией файлов.
Главная механика: гейт
Всё остальное — обвязка вокруг одного правила: ни одной правки исходников, пока нет утверждённого плана.
Держит его механика: PreToolUse‑хук на Edit/Write физически отказывает в вызове, пока в каталоге инцидента нет файла IAP-APPROVED с совпадающим хэшем.
Хэш прибит к содержимому плана. Отредактировали IAP.md после утверждения — sha256 разъехался, утверждение аннулировано механически, гейт снова закрыт. Никакой переменной окружения для обхода нет. Единственный санкционированный способ снять гейт: человек удаляет .dcs/ACTIVE руками.
Внутри проекта есть табличка, которую я считаю самым честным его куском:
|
Правило словами |
Что произошло |
Что реально удержало |
|---|---|---|
|
«Диспетчер обязан советоваться с командиром» |
нарушено 3 раза в первый же день |
чек‑лист перед штампом плюс входные гейты, читающие журнал |
|
«Ревизию делает свежий агент; возобновлять прежнего запрещено» |
нарушено дважды после того, как правило было внедрено |
гейт, запрещающий возобновление сессии с агентом на старом контексте, только полный респаун |
|
«Никакого кода до утверждённого плана» |
— |
|
Единственное правило без нарушений с самого начала было механизмом. Отсюда вывод, вокруг которого потом собралась вся система: текст агента документирует механизм, но не заменяет его.
Офицер по безопасности (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


