Надёжные системы из агентов: граф вместо цепочки Продвинутое
Библиотека/ Claude Code/Продвинутое

Надёжные системы из агентов: граф вместо цепочки

Скопируй страницу и вставь в Claude или GPT — разберёт под твою задачу.

Ты кидаешь Claude Code крупную задачу — «перепиши модуль оплаты, покрой тестами, не сломай остальное». Он бодро уходит в работу на полчаса, а к концу диалога уже забыл, о чём вы договорились в начале, тесты «прогнал» где-то у себя в голове и радостно рапортует: готово. Ты открываешь код — половина не работает, тесты не запускались, а контекст сессии так распух, что переспрашивать бесполезно. Знакомо? Проблема не в модели. Проблема в том, что ты гоняешь один линейный диалог там, где нужна система.

Короткий ответ

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

Приём, которым уже пользуются на фронтире, — перестать думать о работе как о диалоге и смоделировать её как граф. Задачи становятся узлами, зависимости — рёбрами, независимые куски идут параллельными ветками, а отдельные узлы-проверки не верят агенту на слово и сверяют результат по кодам возврата и тестам. В Claude Code это уже встроено — механизм Dynamic Workflows. Дальше разберём лестницу от «проверяемого цикла за пять минут» до полноценной фабрики, и как подниматься по ней постепенно.

Почему линейный агент упирается

Разберём три поломки по отдельности, потому что граф чинит каждую из них своим способом.

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

Последовательность. Один диалог делает одно дело за раз. Если задачу можно разбить на три независимых куска — изучить архитектуру, разобрать тесты, прочитать доки — линейный агент всё равно пройдёт их по очереди. Ты платишь временем за то, что могло идти параллельно.

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

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

Идея графа: четыре понятия

Граф здесь — это DAG, направленный граф без циклов. Звучит академично, но по сути это просто карта работы: коробочки-задачи и стрелки-зависимости, причём стрелки никогда не заворачивают назад в петлю. Чтобы им пользоваться, хватит четырёх понятий.

Узел (node)

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

Ребро (edge)

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

Состояние (state)

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

Проверка (validation)

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

Ключевая мысль: любая параллельная разработка уже граф. Когда команда раздаёт независимые куски задачи, ждёт всех на общей точке и потом сводит результат — это классический fan-out, барьер и fan-in. Разница только в том, что обычно этот граф живёт в голове тимлида, а не записан кодом, который можно прогнать заново.

Первый граф для работы с репозиторием

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

  • Задача — формулируешь, что нужно сделать и по какому критерию считать готовым.
  • Параллельное изучение — три отдельных агента одновременно смотрят архитектуру, тесты и доки. Каждый только читает.
  • План и контракты — сводишь находки в план и фиксируешь интерфейсы: кто с кем как стыкуется.
  • Реализация в изолированном worktree — писатель работает в отдельной рабочей копии, не трогая основную.
  • Тесты, линтер, тайпчек — три независимых сигнала, что код действительно рабочий.
  • Независимое ревью — отдельный агент со свежим контекстом смотрит дифф.
  • Интеграция и финальная проверка — сливаешь в основную ветку и гоняешь полный прогон.

Обрати внимание на одно правило, которое проходит через весь граф.

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

В Claude Code это уже встроено: Dynamic Workflows

Не нужно строить оркестратор с нуля. В Claude Code есть механизм Dynamic Workflows — это JavaScript-сценарии, которые выполняются отдельно от контекста разговора. В этом вся соль: поток управления детерминирован. Циклы, условия, fan-out описаны кодом, а не оставлены «на усмотрение модели». Модель решает, что написать внутри узла, но не решает, сколько узлов запустить и в каком порядке — это уже задано скриптом.

Запускается такой сценарий обычным языком или ключевым словом ultracode. Три главных примитива:

  • agent(prompt) — запустить суб-агента на конкретную подзадачу.
  • parallel([...]) — барьер: запустить пачку узлов разом и дождаться всех.
  • pipeline(items, stage1, stage2) — конвейер: каждый элемент независимо проходит все стадии, без общего барьера между стадиями. Один элемент застрял — остальные не ждут.

Сверху — поддержка JSON-схем на выходе узла (чтобы результат приходил в нужной форме, а не свободным текстом), циклов, бюджета токенов и изолированных worktree. Вот как выглядит параллельное ревью на практике — я гоняю проверку диффа именно так:

const reviews = await parallel([
  () => agent('Проверь дифф на ошибки корректности'),
  () => agent('Проверь безопасность и права доступа'),
  () => agent('Найди дыры в тестах и крайние случаи'),
])

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

Как это же делает Codex

У Codex подход похожий, но с другим акцентом. Там нативные суб-агенты, а роли описываются в файлах .codex/agents/*.toml. Ключевой параметр — sandbox_mode: "read-only": он ставит техническую границу. Агент с этим режимом физически не может писать файлы — не «попросили не трогать», а именно не может. Для узлов-исследователей это идеальный предохранитель.

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

Готовые рантаймы: экосистема уже есть

Хорошая новость — писать движок графов самому не надо. Вокруг Dynamic Workflows уже сложилась экосистема опенсорса. Четыре штуки, которые стоит знать:

  • ultracodex — github.com/YuanpingSong/ultracodex. Гоняет workflow-скрипты Claude через авторизацию Codex. На борту agent(), parallel(), pipeline(), JSON-схемы, циклы, бюджеты токенов и worktree.
  • claude-dynamic-workflows-codex — github.com/scasella/claude-dynamic-workflows-codex. Добавляет журналирование, возобновление после сбоя (resume), человеческие «ворота» и карты выполнения — видно, где граф сейчас находится.
  • open-dynamic-workflows — github.com/xz1220/open-dynamic-workflows. CLI-инструмент odw, валидация графа по схеме и создание worktree из коробки.
  • smithers — github.com/smithersai/smithers. Тяжёлый JSX-подход с durable-выполнением, хранением в SQLite и наблюдаемостью — под долгие процессы, которые обязаны переживать перезапуски.

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

Изоляция через worktree

git worktree — это отдельная рабочая копия того же репозитория, на своей ветке, в своей папке. Несколько таких копий живут параллельно и не мешают друг другу. Для графа это база: каждый пишущий агент получает свой worktree и творит там что хочет, не задевая ни основную копию, ни соседей.

Правил вокруг worktree много, но вот самые важные, без которых начнутся проблемы:

  • Один пишущий агент — один worktree. Двое в одной копии = конфликты.
  • Одна ветка — один worktree. Не вешай две рабочие копии на одну ветку.
  • Основной checkout — только у интегратора. Тот, кто сливает, работает в основной копии; остальные — по своим.
  • Контракты фиксируй ДО расхождения веток. Интерфейсы должны быть согласованы, пока все ещё на общей точке, иначе ветки разъедутся несовместимо.
  • Стартуй с чистого коммита. Не отпускай агента писать поверх грязного состояния — потом не разберёшь, где чьи изменения.
  • Изолируй общие ресурсы. Порты, базы данных, кэши — разведи их между параллельными агентами, иначе они передерутся за один и тот же ресурс.
  • Полный прогон тестов — ПОСЛЕ слияния. Зелёные тесты в отдельной ветке не гарантируют зелёные после merge.
  • Удаляй worktree только после проверки интеграции. Пока не убедился, что слияние прошло чисто, копию не сноси.

Узлы-проверки — сердце надёжности

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

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

Ни один из этих сигналов нельзя «сгенерировать» уверенным тоном. Тест либо прошёл, либо нет.

Ключевая мысль: независимый проверяющий должен оценивать результат по чёткой рубрике и по артефактам, со свежим контекстом — и НЕ видеть, как автор себя хвалил. Если ревьюер читает восторженный отчёт исполнителя, он наследует его уверенность и пропускает то же, что пропустил автор. Свежая голова, которая судит по фактам, а не по настроению, — вот что делает проверку проверкой.

Библиотека паттернов

Из четырёх понятий складывается небольшой набор форм, которые ты будешь узнавать снова и снова. Держи их в голове как готовые кирпичи:

  • Последовательная цепочка — анализ → план → реализация → тест. Простейшая линия, но с явными узлами и проверками.
  • Fan-out / fan-in — задача дробится на независимые части, они идут параллельно, результаты сходятся на барьере.
  • Pipeline (конвейер) — каждый элемент проходит стадии сам по себе, не дожидаясь остальных.
  • Маршрутизация (routing) — условное ветвление: простой кейс идёт коротким путём, рискованный — длинным с дополнительными проверками.
  • Генерация-и-фильтр — несколько агентов предлагают варианты, а тесты или судьи выбирают лучший.
  • Цикл починки (repair loop) — повтор попыток с жёстким лимитом и бюджетом, пока не станет зелено.
  • Состязательная проверка (adversarial) — отдельный ревьюер-скептик, независимый от исполнителя, чья работа — искать дыры, а не хвалить.

За пределами кода

Легко подумать, что граф-инженерия — это про разработку. На деле те же конструкции работают для любого процесса, где есть независимые параллельные куски, чекпоинты и точки, где нужен живой человек. Поддержка и разбор тикетов, анализ инцидентов, обработка документов пачкой, контент-пайплайны — всё это раскладывается на те же узлы, рёбра, состояние и проверки. Если в задаче есть «эти три куска не зависят друг от друга» и «здесь нужно свериться, прежде чем идти дальше» — граф применим.

Надёжность фабрики

Верхний этаж этой лестницы — «фабрика»: очередь задач, которая сама раскидывает работу по worktree, гонит код через CI и выкатывает пул-реквесты. Чтобы такое не разваливалось, нужен набор гарантий:

  • Задача и критерий готовности живут вне контекста модели — в состоянии, а не в памяти агента.
  • У каждого узла есть статус — pending, running, success, failed, blocked, aborted. Ты всегда видишь, где что стоит.
  • Чекпоинты и артефакты после значимых узлов — есть куда откатиться и что предъявить.
  • Идемпотентность — повтор узла не плодит дубли PR или, тем более, повторных платежей.
  • Ретраи под тип ошибки и бюджет — сетевой сбой ретраим, логическую ошибку нет; и всё под лимитом.
  • Действия высокого риска — через человеческие ворота. Деплой в прод или необратимая операция ждут живого «да».
  • Аудит-логи — версия модели, инструкции, инструменты, потраченные токены, результаты проверок. Потом разберёшься, что именно произошло.
  • Новый прогон привязан к версии графа и набору правил — воспроизводимость, а не «в тот раз почему-то сработало».
Ключевая мысль: детерминизм здесь про ТОПОЛОГИЮ, а не про выход LLM. Ты фиксируешь, какие узлы есть, где параллель, где барьер, каковы лимиты ретраев — а не пытаешься заставить модель выдавать одинаковый текст. Критичные инварианты держи тестами, схемами, хешами и политиками в коде, а не проверкой свободным текстом. Свободный текст врёт уверенно; код возврата — нет.

Когда граф преждевременен

Честно: граф — не всегда ответ. Один агент в чате лучше на мелких правках, коротких разборах и живом интерактивном исследовании, когда ты сам ведёшь диалог и на ходу меняешь направление. Городить топологию ради правки в три строки — оверинжиниринг.

Граф окупается, когда сходится несколько из этих условий:

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

Лестница внедрения: пять уровней

Главное — не прыгать сразу на фабрику. Поднимайся по ступеням, каждая даёт пользу сама по себе.

Уровень 1. Проверяемый цикл

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

Уровень 2. Параллельное изучение

Несколько суб-агентов только на чтение расходятся по коду — архитектура, тесты, доки — и сходятся в единый план. Быстрее линейного разбора и безопасно, потому что никто ничего не пишет.

Уровень 3. Изолированная разработка

Пишущие агенты работают в отдельных worktree с заранее зафиксированными контрактами. Теперь можно вести несколько кусков параллельно, не боясь, что они передерутся за файлы.

Уровень 4. Исполняемый граф

Топология описана на JavaScript — с валидацией выходов по схемам и барьерами между фазами. Процесс перестаёт зависеть от того, «догадается» ли модель, и становится воспроизводимым.

Уровень 5. Фабрика

Очередь, устойчивое состояние, CI, автоматические PR, человеческие ворота на рискованных шагах, аудит и откат. Это уже инфраструктура, а не приём — и лезть сюда стоит, только когда нижние уровни реально жмут.

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

Вывод

Граф-инженерия начинается с малого и не требует переписывать твою работу с нуля. Разведи три фазы, которые линейный агент склеивает в одну: чтение, запись и проверку. Изолируй параллельных писателей в отдельных worktree, чтобы они не мешали друг другу. И запрети исполнителю самому себе ставить зачёт — пусть готовность подтверждает независимый сигнал, а не уверенный текст. Три этих шага превращают разработку из «помолись на один агент и надейся» в систему, которую видно насквозь, которую можно прогнать заново одинаково и которую не страшно расширять.