Графовая инженерия с Claude: 14-шаговая дорожная карта от нуля до архитектора графов (полный курс)
Полный перевод X Article о графовой инженерии с Claude.
Источник: https://x.com/0xCodez/status/2079165300625330317
Автор: Codez
Большинство людей, которые пытаются построить многошагового агента, в итоге получают прямую линию. Шаг один, шаг два, шаг три — каждый вежливо ждёт, пока предыдущий закончит, прежде чем начаться самому.
9 из 10 замечают, что половине этих шагов вообще не нужно было ждать.
Они не маршрутизируют. Не ветвятся. Не распараллеливаются. Они просто выстраиваются в очередь — одна голова, один контекст, одно действие за раз, пока окно не заполнится и агент не забудет, чем занимался.
Подписывайтесь на мой Substack, чтобы получать свежую AI-альфу: movez.substack.com
Это 14-шаговая дорожная карта, которая превращает такую однофайловую линию в граф: граф, который расходится веером по целому флоту, проверяет собственные выводы и сходится к результату, который одинокий агент никогда не смог бы удержать.

Вот сдвиг, о котором никто не говорит прямо. Промпт — это предложение. Цикл — это повтор. Обвязка — это пол, на котором стоит агент.
Но сама форма работы — что запускается перед чем, что может идти одновременно, что обязано ждать всего остального, — эта форма и есть граф. Узлы думают. Рёбра переносят результаты.
Claude Code поставил инструменты, чтобы строить такие графы напрямую: dynamic workflows.
Claude пишет простой JavaScript-скрипт оркестрации, затем запускает скоординированный флот субагентов для его выполнения — и сама координация стоит ноль модельных токенов, потому что это код, а не разговор.
01. Узлы — это задачи. Рёбра — это то, что течёт.
У графа есть ровно две вещи, и если их правильно развести, большая часть путаницы исчезает. Узел — это единица работы: один агент, одна ограниченная задача, один вход и один выход.
Ребро — это зависимость: оно говорит, что выход этого узла подаётся на вход того узла. И ничего больше.

Ошибка — считать «а затем» ребром. «Суммируй файл, а затем скажи мне погоду» не имеет ребра между двумя шагами: погода не потребляет summary.
Это два несвязанных узла, которые линейный скрипт без нужды сцепляет. Ребро существует только тогда, когда по нему действительно движутся данные.
Учитесь спрашивать для каждого «а затем» в вашем агенте: следующий шаг читает выход предыдущего? Если нет — ребра нет, и ожидание потрачено впустую.
Нарисуйте это как прямоугольники и стрелки. Прямоугольник — это вызов agent().
Стрелка — это переменная, переданная из результата одного вызова в prompt
другого. Если вы не можете нарисовать стрелку — если никакая переменная
не пересекает границу, — два прямоугольника независимы, а независимость —
это то, что вы будете использовать до конца курса.
02. Ваш линейный скрипт — вырожденный граф
Когда вы пишете агента как «сделай A, затем B, затем C, затем D», вы уже нарисовали граф — единственную неразветвлённую цепочку. У каждого узла ровно одно входящее ребро и одно исходящее.
Он работает корректно. Но он также работает медленно и хрупко, потому что у цепочки нет избыточности: если C зависнет, D никогда не случится, а работа A останется запертой выше по потоку без возможности куда-то уйти.

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

В workflow этот контракт обеспечивается схемой. Когда вы даёте Claude вызов agent() с JSON-схемой, субагент, которого запускает Claude, вынужден вернуть валидированные структурированные данные — валидация происходит на уровне tool-call, поэтому Claude повторяет попытку при несовпадении, вместо того чтобы отдать вам свободный текст, который придётся парсить и молиться.
Это разница между узлом, который Claude может встроить в граф, и узлом, который работает только тогда, когда человек читает его вывод.
// Узел с настоящим контрактом: ограниченный вход, валидированный выход, одна задача.
const ITEM = {
type: 'object', additionalProperties: false,
properties: {
title: { type: 'string' },
url: { type: 'string' },
impact: { type: 'string', enum: ['high', 'medium', 'low'] },
},
required: ['title', 'url', 'impact'],
};
const result = await agent(source.prompt, {
label: `research:${source.key}`,
schema: ITEM, // заставляет вернуть валидированный структурированный вывод
agentType: 'general-purpose',
});
// result теперь имеет форму, которой следующий узел может доверять, — не свободный текст.04. Относитесь к ребру как к контракту данных
Ребро — это не просто «B идёт после A». Это обещание о том, что пересекает границу: A производит такую форму, а B создан, чтобы потреблять такую форму. Когда вы называете ребро по его данным, а не по порядку, две вещи становятся проще.

Вы сразу видите, реально ли ребро (данные действительно движутся?), и можете заменить узел на любом конце, не ломая граф, пока форма сохраняется.
На практике ребро живёт в обычном JavaScript. Шаг reduce между fan-out и синтезом — flatten, dedupe, filter — это просто код, работающий с формами, которые вернули узлы.
Агент не нужен. Одна из тихих побед графового мышления: огромная часть того, на что люди сжигают модельные токены, на самом деле является ребром, а рёбра бесплатны.
Соблазн — запустить агента, чтобы «объединить результаты». Сопротивляйтесь
ему. Если объединение означает flatten-and-dedupe, это results.flatMap(...)
и Set — детерминированно, мгновенно, ноль токенов. Берегите агентов для
суждения, не для сантехники. Граф, где каждое ребро — агент, платит аренду
за собственную проводку.
05. Расходитесь веером с parallel()
Это приём, который окупает всё остальное. Когда у вас есть N независимых узлов — N источников для проверки, N файлов для ревью, N маршрутов для аудита — вы не сцепляете их в цепочку.
Вы говорите Claude разойтись веером и запустить их одновременно. В workflow это parallel(): Claude берёт массив thunk-функций и запускает по одному субагенту на каждый thunk, все выполняются конкурентно, а затем возвращает вам массив результатов.

Две детали делают это надёжным. Во-первых, parallel() — это барьер: он ждёт каждый thunk перед возвратом, так что следующая стадия видит полный набор. Во-вторых, thunk, который выбрасывает ошибку, разрешается в null вместо того, чтобы отклонить весь batch, поэтому один нестабильный агент не может потопить запуск.
Всегда делайте .filter(Boolean) для результатов. Конкурентность ограничена примерно числом ваших ядер, а избыток ставится в очередь, так что можно передать сотню thunk-функций — они все завершатся, просто по несколько за раз.
phase('Research');
// Девять источников, девять агентов, все одновременно.
const raw = await parallel(
SOURCES.map((s) => () =>
agent(s.prompt, {
label: `research:${s.key}`,
phase: 'Research',
schema: ITEM_SCHEMA, // каждый узел возвращает валидированный JSON
agentType: 'general-purpose',
}),
),
);
const collected = raw.filter(Boolean); // отбрасываем null от упавших агентовFan-out живёт в коде, который написал Claude, а не в модельном разговоре. Собственный контекст Claude никогда не держит девять источников одновременно — каждый субагент несёт свой, и обратно возвращается только финальный ответ.
Именно это позволяет Claude масштабировать workflow до десятков или сотен субагентов, не утопив сессию. Слой оркестрации стоит ноль токенов, потому что это не ещё один ход мышления Claude.
06. Сходиться веером у барьера
Fan-out полезен только если кто-то его собирает. Fan-in — это узел, где рёбра сходятся: где один агент (или один кусок кода) видит все upstream-результаты сразу и делает что-то, чему нужен весь набор: дедупликация по источникам, ранжирование по impact, ранний выход, если всё вернулось пустым. Это единственное место, где барьер заслуживает своей цены во времени.

Правило, которое сохраняет графы быстрыми: используйте барьер только тогда, когда стадии действительно нужны все предыдущие результаты вместе. Дедупликация по всем источникам? Барьер — корректно.
// Ребро: обычный JS, без агента, ноль токенов.
const flat = collected.flatMap((c) => c.items);
log(`Собрано ${flat.length} элементов`);
phase('Curate');
// Барьерный узел: ему нужен ВЕСЬ набор, чтобы dedupe + rank.
const curated = await agent(
`Дедуплицируй и ранжируй это по impact:\n${JSON.stringify(flat)}`,
{ phase: 'Curate', schema: CURATED_SCHEMA },
);Просто расплющить список? Это ребро, делайте inline. Тест на запах жестокий и простой: если вы написали parallel → transform → parallel, и у среднего transform нет зависимости между элементами, нужно было использовать pipeline и вообще пропустить барьер.
07. Ромб: split → work → merge
Соедините fan-out и fan-in — и получите рабочую топологию каждого серьёзного агентного графа: ромб.
Один узел делит задачу, многие узлы выполняют работу параллельно, один узел объединяет. Это форма, лежащая за market scan, dependency audit, code review, research report — меняйте источники и промпты, и тот же скелет адаптируется.

У канонической формы есть имя, которое стоит запомнить: fan out → reduce → synthesize. Расходиться веером, чтобы собрать ширину; сжимать обычным кодом; синтезировать финальным агентом, чтобы написать ответ.
Как только вы видите ромб, вы перестаёте спрашивать «как заставить моего агента делать больше шагов» и начинаете спрашивать «где split, где merge» — а это вопрос, который действительно масштабируется.
08. Маршрутизируйте ребро во время выполнения условием
Не каждый граф фиксирован. Иногда то, какое ребро выбрать, зависит от найденного узлом. Узел-маршрутизатор проверяет результат и решает, какой downstream-путь сработает: классифицировать тикет, затем уйти к правильному обработчику; проверить размер diff, затем либо сделать быстрое ревью, либо поднять полный аудит.
В workflow это просто JavaScript if или switch по валидированному выходу узла, потому что control flow живёт в коде.

Здесь детерминизм становится преимуществом, а не ограничением. Решение маршрутизатора может быть powered by Claude (субагент классифицирует), но сама маршрутизация — это код, который написал Claude, поэтому для одной и той же классификации он каждый раз работает одинаково.
Вы получаете суждение Claude в узле и надёжность скрипта на ребре. Никаких неожиданных «Claude решил пропустить аудит» — потому что пропуск пришлось бы явно записать в граф, а его там нет.
// Узел-маршрутизатор: агент классифицирует, код выбирает ребро.
const { severity } = await agent(
`Классифицируй риск этого diff:\n${diff}`,
{ schema: { type: 'object',
properties: { severity: { enum: ['low', 'high'] } },
required: ['severity'] } },
);
let review;
if (severity === 'high') {
// тяжёлый путь: полный параллельный аудит
review = await parallel(FILES.map((f) => () => agent(`Аудит ${f}`)));
} else {
// лёгкий путь: один быстрый проход
review = await agent(`Быстрое ревью ${diff}`);
}09. Поставьте верификатор на ребро
Настоящий рычаг графа — не «больше агентов», а структура, которую можно обернуть вокруг них, чтобы получить уверенность.
Узел-верификатор сидит на ребре перед тем, как результату позволят идти downstream, и его единственная задача — попытаться убить находку. Если она выживает, она проходит. Если нет — она никогда не попадает в ответ.

Три паттерна стоит держать под рукой.
- Adversarial verify: для каждой находки запустите N независимых скептиков с промптом опровергнуть её; оставляйте её только если большинство выжило.
- Perspective-diverse verify: дайте каждому верификатору отдельную линзу — correctness, security, does-it-reproduce, — потому что разнообразие ловит классы отказов, которые N одинаковых проверок никогда не поймают.
- Judge panel: сгенерируйте N попыток с разных углов, оцените их параллельными судьями, синтезируйте из победителя, прививая лучшее от занявших следующие места.
Это ровно тот паттерн, который позволил реальной команде портировать runtime Bun со встроенным в цикл adversarial code review.
10. Изолируйте узлы, чтобы один сбой не отравил граф
В цепочке сбой каскадирует: C умирает, D никогда не запускается, всё останавливается. В графе сбой должен быть ограничен своим узлом.
Это уже частично верно: thunk, который выбрасывает ошибку внутри parallel(), разрешается в null, так что восемь хороших агентов всё равно возвращаются, пока один плохой выпадает. Ваш .filter(Boolean) — это containment.
Проектируйте каждый fan-in так, чтобы он терпел отсутствующие входы, а не предполагал полный набор.

Более тонкий сбой — когда узлы мешают друг другу. Когда агенты пишут файлы параллельно, они могут столкнуться.
Исправление — изоляция: "worktree" — каждый агент запускается в собственном git worktree, делает работу в sandbox и чисто сливается.
Беритесь за это только когда узлы действительно пишут параллельно. Это ремень безопасности для единственной топологии, которой он нужен, а не налог по умолчанию на каждый запуск.
11. Добавьте цикл — но заставьте его сходиться
Иногда вы не знаете размер задачи, пока не окажетесь внутри: discovery неизвестного размера, sweep по багам, где один баг раскрывает ещё три. Для этого нужен цикл — контролируемое ребро обратно к более раннему узлу.
Опасность очевидна: цикл, который не сходится, — это бесконечный loop, запускающий агентов, пока бюджет не исчезнет.

Паттерн, который сходится, — loop-until-dry: продолжайте запускать finders, пока K последовательных раундов не находят ничего нового, затем остановитесь. Деталь, которая решает всё — и ошибка, которую почти все делают в первый раз, — это то, против чего вы дедуплицируете.
Дедуплицируйте против всего увиденного, а не только против подтверждённых результатов. Иначе отклонённые находки будут появляться каждый раунд, цикл никогда не высохнет, и вы построите машину, которая платит за повторное обнаружение одних и тех же тупиков снова и снова.
const seen = new Set(); const confirmed = []; let dry = 0;
while (dry < 2) { // остановиться после 2 пустых раундов
const found = (await parallel(
FINDERS.map((f) => () => agent(f.prompt, { schema: BUGS }))
)).filter(Boolean).flatMap((r) => r.bugs);
const fresh = found.filter((b) => !seen.has(key(b)));
if (!fresh.length) { dry++; continue; } // ничего нового → движемся к dry
dry = 0;
fresh.forEach((b) => seen.add(key(b))); // dedupe против SEEN, не confirmed
// diverse-lens verify каждой свежей находки до того, как она засчитается
const judged = await parallel(fresh.map((b) => () =>
parallel(['correctness', 'security', 'repro'].map((lens) => () =>
agent(`Оцени "${b.desc}" через ${lens} — реально?`, { schema: VERDICT })))
.then((v) => ({ b, real: v.filter(Boolean).filter((x) => x.real).length >= 2 }))));
confirmed.push(...judged.filter((v) => v.real).map((v) => v.b));
}12. Распределяйте модели по уровням узлов
Не каждому узлу нужна ваша лучшая модель. Граф делает это очевидным так, как одиночный агент никогда не сделает: некоторые узлы ограниченные и повторяющиеся (извлечь это поле, классифицировать этот тикет), а некоторые несут настоящее суждение (синтезировать отчёт, вынести решение по находке).
Запускайте скучные узлы на более дешёвой модели и тратьте дорогие токены там, где действительно живёт суждение.

В workflow каждый субагент, которого запускает Claude, наследует модель вашей сессии, если скрипт не переопределит её, — так что по умолчанию большой запуск целиком биллится на уровне вашей сессии. Опция model в отдельном вызове agent() говорит Claude направить только этот узел в другое место.
Проверьте /model перед крупным запуском, затем попросите Claude опустить повторяющиеся узлы fan-out на более дешёвую модель и оставить merge-узел наверху. Это рычаг, который превращает прожорливый по токенам граф из дорогого в экономичный, не меняя его форму.
13. Топология — это ваши cost и latency
Форма графа — не косметика, а главный рычаг wall-clock time. Выбор, на котором спотыкаются все: parallel() против pipeline(). Барьер parallel() заставляет всё ждать самый медленный узел, прежде чем начнётся следующая стадия.
pipeline() прогоняет каждый элемент через все стадии независимо, без барьера: элемент A может быть на стадии 3, пока элемент B ещё на стадии 1. Быстрые элементы заканчивают раньше, а не простаивают за медленными.

По умолчанию выбирайте pipeline(). Беритесь за барьер только когда стадии действительно нужны все предыдущие результаты сразу — cross-set dedupe, early-exit по общему числу, prompt, который сравнивает с «другими находками». «Так код чище» и «стадии ощущаются отдельными» — не причины; barrier latency реальна, измерима и тратит время впустую. Отдельное — не значит синхронизированное.
14. Позвольте Claude нарисовать граф — self-routing
Финальный приём — перестать рисовать граф вручную для задач, которые невозможно спланировать заранее.
С dynamic workflows вы описываете цель, и Claude сам пишет скрипт оркестрации: декомпозирует задачу, выбирает fan-out, запускает скоординированный флот субагентов и синтезирует результат. Вы получаете граф, подогнанный под конкретный запуск, а не фиксированный граф, который вы надеялись применить.

Есть три входа. Скажите слово “workflow” в промпте, и Claude напишет его под задачу. Запустите сохранённый или bundled workflow — /deep-research является реальным графом, который уже поставляется в production: scope → parallel search → fetch → adversarial verify → synthesize, ровно тот скелет, что в этом курсе.
Или включите ultracode, и Claude будет планировать workflow для каждой существенной задачи в сессии. Когда запуск хорош, нажмите s, чтобы сохранить его скрипт в .claude/workflows/ — version-controlled, повторно запускаемый по имени граф, который может запустить любой, кто клонирует repo.
› Запусти workflow, чтобы проверить каждый route в src/routes/ на отсутствующую
auth. Запусти по одному агенту на каждый route file, затем проверь каждую
находку перед отчётом. ● Claude написал скрипт оркестрации · запускается в
background… /workflows — auth-audit · running ✓ Scope 1/1 2.1k tok ·
4s ✓ Fan-out 18/18 one agent per route file ◯ Verify 11/18 3-vote
skeptics per finding… ○ Synthesize 0/1 waiting on verify session stays
responsive — keep working while the fleet runsШесть графов, которые стоит построить с Claude на этой неделе

- Security sweep по каждому route. Claude запускает по одному субагенту на каждый route file, каждый ищет отсутствующие auth checks, затем проход верификатора подтверждает каждую находку до того, как она попадёт в отчёт. Ширина, которую не удержал бы ни один одиночный контекст.
- Cited report с /deep-research. Граф, который уже поставляется в Claude Code. Claude декомпозирует ваш вопрос на отдельные углы, запускает параллельные searches, дедуплицирует sources, затем adversarially verifies каждое claim с трёхголосыми скептиками перед написанием.
- Портируйте module, file by file. Потолок Bun, масштабированный на ваш repo. Claude разносит translation по файлам, запускает test suite как gate на каждом и зацикливает failures обратно — adversarial review ловит то, что одиночный pass отправил бы сломанным.
- Adversarial review of a diff. Claude маршрутизирует по размеру diff: маленькое изменение получает один quick pass, большое запускает полный параллельный audit с reviewers по отдельным lenses — correctness, security, performance — затем judge panel синтезирует.
- Ecosystem scan по расписанию. Сохраните один раз, запускайте вечно. Claude проверяет множество sources параллельно — releases, blogs, discussion — ранжирует по impact у барьера и пишет digest. Version-controlled в .claude/workflows/, запускается по имени.
- Discovery неизвестного размера. Вы не знаете, сколько там bugs. Claude запускает finders параллельно, дедуплицирует каждую новую находку против всего seen, verifies survivors и продолжает loop, пока два раунда не принесут ничего нового — затем останавливается.
Заключение:
Промптер задаёт вопрос. Архитектор рисует граф.
Линейный агент никогда не был потолком — это была просто первая форма, к которой все тянутся, потому что она похожа на то, как мы печатаем. Одна линия, одна голова, одно действие за раз.
Как только вы видите узлы и рёбра, вы перестаёте просить агента сделать больше и начинаете просить граф сделать шире: расходиться веером там, где работа независима, ставить gates на рёбра там, где важна уверенность, распределять модели по уровням там, где суждение не нужно.
Большинство продолжит выстраивать шаги в линию. Те, кто научится рисовать граф, будут запускать флот — и никогда не заметят потолок, под которым застряли остальные.