Все, кто строит мультиагентные системы в 2026 году, всё ещё пишут прямые линии. Шаг первый, затем шаг второй, затем шаг третий — каждый ждёт предыдущий. Вот почему это медленно и как это исправить.

Проблема, которую никто не проверяет
Вы построили многошагового агента. Он работает. И он медленный.
Вы думаете, что узкое место — модель. Это не так.
Узкое место — это форма, которую вы нарисовали. Цепочка — шаг 1 ждёт шаг 2, шаг 2 ждёт шаг 3 — заставляет последовательное выполнение, даже когда половина этих шагов никак не связаны друг с другом.
«Суммируй этот документ, затем проверь погоду» — это две независимые задачи, наряженные в плащ одного рабочего процесса. Задача с погодой не нуждается в сводке. Никогда и не нуждалась. Но если вы записали это как цепочку, она всё равно ждёт.
Это бесполезное ожидание, умноженное на десятки шагов, — вот куда уходит большая часть времени выполнения.
Глава 1 — Циклы против графов
Цикл — это одна единица самосовершенствования:
1попробовать что-то → проверить результат → скорректировать → попробовать снова
Это атом. Один агент, одна метрика, цикл до сходимости.
У циклов есть известный режим отказа: они оптимизируют ровно то, что вы измеряете, и ничего больше. Чат-бот поддержки, настроенный на быстрое закрытие тикетов, будет быстро закрывать тикеты — пока удовлетворённость незаметно не рухнет. Цикл не видит ничего за пределами своей метрики. Это закон Гудхарта в вашей архитектуре агента.
Граф исправляет это по своей сути. Вместо одного цикла, гоняющегося за одним числом, вы строите сеть циклов, которые следят друг за другом и корректируют. Вывод узла A подаётся на вход узла B. Узел C работает независимо и проверяет оба. Ни одна метрика не управляет всей системой — это делает структура.

Для агентных систем это означает один конкретный сдвиг: перестаньте писать одного агента, который делает всё сверху донизу. Сначала спроектируйте форму работы — что должно произойти до чего, что может выполняться одновременно, что действительно должно ждать.
Глава 2 — Узлы, рёбра и тест, который их разделяет
Граф состоит ровно из двух компонентов:
Узел — одна единица работы. Один агент, одно задание, один вход, один выход.
Ребро — реальная зависимость. Вход узла B требует выход узла A.
Ошибка, которую допускают почти все: считать «а затем» ребром по умолчанию.
1«Прочитай эту кодовую базу и затем напиши список изменений»2«Загрузи страницу с ценами и затем суммируй фичи конкурентов»
Задайте один вопрос для каждого «а затем» в вашем рабочем процессе:
Действительно ли следующий шаг читает вывод предыдущего шага?
Если да → настоящее ребро. Сохраняйте последовательный порядок. Если нет → ребра нет. Ожидание впустую. Запускайте их параллельно.
Если никакие данные не пересекают границу между двумя задачами, они независимы — и каждая независимая пара, которую вы выполняете последовательно, — это потраченное впустую время выполнения.
Вот тест в коде:
1from dataclasses import dataclass23@dataclass4class TaskNode:5 id: str6 prompt: str7 depends_on: list[str] # ID узлов, которые этому реально нужны89def has_real_edge(node_a: TaskNode, node_b: TaskNode) -> bool:10 """11 Основной тест графовой инженерии:12 действительно ли промпт node_b требует вывод node_a?13 """14 return node_a.id in node_b.depends_on1516# Пример: большинство «цепочек» сворачиваются в 2-3 группы реальных зависимостей17nodes = [18 TaskNode("audit_routes", "Список всех файлов маршрутов API", []),19 TaskNode("check_auth", "Проверить покрытие auth middleware", []),20 TaskNode("fetch_weather", "Получить сегодняшнюю погоду", []),21 TaskNode("summarize", "Суммировать результаты маршрутов и аудита",22 depends_on=["audit_routes", "check_auth"]),23]2425# audit_routes, check_auth, fetch_weather НЕ имеют рёбер между собой26# Они выполняются параллельно. Только "summarize" имеет реальные рёбра — он ждёт.
Ваш текущий агент «сделай A, затем B, затем C» технически уже является графом. Просто это наихудший из возможных — одна цепочка, где если C зависает, ничего ниже по потоку никогда не выполняется.
Глава 3 — Строим ваш первый граф

Требования:
- Claude Code (последняя версия с поддержкой Dynamic Workflows).
- План Max, Team или Enterprise — рабочие процессы включены по умолчанию. На плане Pro включите вручную.
Откройте реальный репозиторий. Не игрушечный пример — результат проявится только в реальном масштабе.
Промпт, который запускает ваш первый граф:
1Создай рабочий процесс для аудита каждого файла маршрутов в этой кодовой базе.23Для каждого файла маршрутов проверь независимо:4- наличие middleware аутентификации5- валидацию входных данных на всех параметрах6- настройку ограничения скорости (rate limiting)7- обработку ошибок, не раскрывающую стектрейсы89Запусти эти проверки параллельно для всех файлов маршрутов —10они не зависят друг от друга.1112После того как все файлы проверены, создай один сводный13отчёт, сгруппированный по серьёзности: критический, предупреждение, информация.1415Этап сводки должен ждать завершения всех проверок.16Всё до него — не должно.
Обратите внимание на структуру, встроенную в сам промпт: параллельная работа явно указана, одна реальная зависимость (сводка ждёт все проверки) явно названа. Вы не надеетесь, что агент сам выведет граф — вы его описываете.
Что происходит под капотом — упрощённая версия оркестрации:
1import asyncio2from anthropic import Anthropic34client = Anthropic()56async def audit_route_file(filepath: str) -> dict:7 """Один узел. Выполняется независимо от любого другого файла маршрутов."""8 response = await client.messages.create(9 model="claude-sonnet-5",10 max_tokens=1000,11 messages=[{12 "role": "user",13 "content": f"""Проверь этот файл маршрутов на:14 - auth middleware, валидацию ввода,15 ограничение скорости, обработку ошибок1617 Файл: {filepath}1819 Верни JSON: {{"file": "", "issues": [], "severity": ""}}"""20 }]21 )22 return {"file": filepath, "result": response.content[0].text}2324async def consolidate(results: list[dict]) -> str:25 """Единственное реальное ребро — ждёт завершения каждого узла аудита."""26 response = await client.messages.create(27 model="claude-opus-4-8",28 max_tokens=2000,29 messages=[{30 "role": "user",31 "content": f"""Объедини эти {len(results)} аудитов маршрутов32 в один отчёт, сгруппированный по серьёзности:3334 {results}"""35 }]36 )37 return response.content[0].text3839async def run_graph(route_files: list[str]):40 # Разветвление — все независимые узлы выполняются конкурентно41 audit_tasks = [audit_route_file(f) for f in route_files]42 results = await asyncio.gather(*audit_tasks)4344 # Схождение — один узел с реальной зависимостью45 report = await consolidate(results)46 return report4748# 40 файлов маршрутов, один промпт, один параллельный проход49results = asyncio.run(run_graph([50 f"routes/{f}.py" for f in ["auth", "users", "billing", "orders"]51 # ...ещё 36
40 последовательных API-вызовов по ~8 секунд каждый — это более 5 минут. Те же 40 вызовов, развёрнутых параллельно: менее 15 секунд, ограничено вашим самым медленным файлом, а не суммой всех.
Глава 4 — Где графы на самом деле ломаются
Графовая инженерия терпит неудачу в трёх предсказуемых местах. Знайте их, прежде чем столкнётесь.
Коллапс контекста. Разверните 1000 узлов и попытайтесь скормить все 1000 выводов одному этапу сводки — вы выйдете за пределы любого контекстного окна ещё до начала синтеза. Исправление: многоуровневое схождение. Группируйте узлы в пакеты по 20–50, суммируйте каждый пакет, затем объединяйте сводки — а не сырые выводы.
1async def layered_consolidate(results: list[dict], batch_size: int = 30):2 """Схождение по уровням — никогда не синтезируйте сырые выводы в масштабе."""3 batches = [results[i:i+batch_size]4 for i in range(0, len(results), batch_size)]56 batch_summaries = await asyncio.gather(*[7 summarize_batch(batch) for batch in batches8 ])910 # Финальная сводка работает со сводками, а не с 1000 сырых результатов11 return await consolidate(batch_summaries)
Ложная независимость. Вы будете считать два узла независимыми, потому что их промпты не ссылаются друг на друга — но оба пишут в один и тот же файл или обращаются к одному API с ограничением скорости. Это скрытое ребро. Исправление: проверяйте общие ресурсы, а не только общие данные. Два узла с конфликтом записи нуждаются в ребре, даже если нет зависимости по данным.
Тихий отказ узла. В цепочке один сбой останавливает всё — раздражающе, но очевидно. В графе один отказавший узел из 200 может исчезнуть в отчёте, который выглядит полным. Исправление: каждый этап схождения проверяет количество узлов относительно ожидаемого перед синтезом и явно отмечает пробелы, вместо того чтобы тихо работать с неполными данными.
1async def safe_consolidate(results: list[dict], expected_count: int):2 if len(results) < expected_count:3 missing = expected_count - len(results)4 print(f"ПРЕДУПРЕЖДЕНИЕ: {missing} узлов отказали молча. "5 f"Отчёт будет неполным.")6 return await consolidate(results)
Глава 5 — Масштабирование до реального флота

Как только шаблон работает на 40 узлах, масштабирование до сотен — это изменение конфигурации, а не перепроектирование, при условии, что вы правильно построили граф, начиная с Главы 2.
Полная форма для продакшена:
1 Orchestrator2 |3 +--------+-------+-------+--------+4 v v v v v5 Node 1 Node 2 Node 3 ... Node N6 (parallel, no edges between any of them)7 | | | |8 +--------+-------+-------+-------+9 v10 Batch Summary <- layered fan-in11 (groups of 30)12 v13 Final Report <- the one true edge
Единственная работа оркестратора: разложить задачу на узлы, определить реальные рёбра и отправить на выполнение. Он сам не работает — он рисует граф.
1async def orchestrate(task: str, resources: list[str]):2 """3 Узел оркестратора — декомпозирует, не выполняет.4 """5 plan = await client.messages.create(6 model="claude-opus-4-8",7 max_tokens=2000,8 messages=[{9 "role": "user",10 "content": f"""Задача: {task}11 Доступные ресурсы: {resources}1213 Разложи на граф:14 - Перечисли каждый независимый узел (без общих рёбер)15 - Перечисли любые реальные зависимости между узлами16 - Сгруппируй узлы в пакеты схождения, если количество > 501718 Верни JSON с: nodes, edges, batch_groups"""19 }]20 )2122 graph = parse_plan(plan.content[0].text)2324 # Выполнение независимых узлов параллельно25 node_results = await asyncio.gather(*[26 execute_node(n) for n in graph["nodes"] if not n["depends_on"]27 ])2829 # Затем выполнение зависимых узлов, соблюдая только реальные рёбра30 final = await execute_dependent_chain(graph["edges"], node_results)3132 return final
Именно это и представляет собой реальный сдвиг графовой инженерии: вы перестаёте быть тем, кто пишет каждый шаг, и становитесь тем, кто проектирует структуру зависимостей. Агенты заполняют узлы. Вы владеете рёбрами.
Что меняется, когда вы мыслите графами, а не линиями
Линейный агент с 40 шагами имеет 40 точек последовательного отказа и задержку в 40 раз больше, чем его самый медленный отдельный шаг.
Граф с теми же 40 единицами работы имеет столько точек параллельного отказа, сколько у вас реальных зависимостей — обычно от 3 до 5 в большинстве рабочих процессов — и задержку, ограниченную вашим самым медленным слоем, а не общим количеством шагов.
Это не просто ускорение. Это разница между рабочим процессом, который занимает 5 минут, и тем, который занимает 15 секунд, выполняя ту же самую работу.
Модель никогда не была узким местом. Линия, которую вы нарисовали, была.
Это технический разбор паттернов мультиагентной оркестрации по состоянию на июль 2026 года. Примеры кода являются иллюстративными — адаптируйте обработку ошибок, ограничение скорости и логику повторных попыток под ваше окружение перед развёртыванием в масштабе.
Спасибо за чтение.


![[Заметка] Начальники избавляются от неэффективных подчиненных](/cdn-cgi/image/width=1920,quality=90,format=auto,metadata=none/https%3A%2F%2Fcms-assets.youmind.com%2Fmedia%2F1784827522698_408j7z_HN3Kb76awAAvvjF.jpg)


