Усі, хто будує багатоагентні системи у 2026 році, досі пишуть прямими лініями. Крок перший, потім крок другий, потім крок третій — кожен чекає на попередній. Ось чому це повільно, і як це виправити.

Проблема, яку ніхто не перевіряє
Ви побудували багатокрокового агента. Він працює. Але він повільний.
Ви думаєте, що вузьке місце — це модель. Але це не так.
Вузьке місце — це форма, яку ви намалювали. Ланцюжок — крок 1 чекає на крок 2, крок 2 чекає на крок 3 — змушує послідовне виконання, навіть якщо половина цих кроків не мають один до одного жодного відношення.
«Підсумуй цей документ, потім перевір погоду» — це дві незалежні задачі, які прикидаються одним робочим процесом. Завданню про погоду не потрібен підсумок. Воно ніколи його й не потребувало. Але якщо ви записали це як ланцюжок, воно все одно чекає.
Ось це марне очікування, помножене на десятки кроків, — саме там зникає більшість вашого часу виконання.
Розділ 1 - Цикли проти графів
Цикл — це одна одиниця самовдосконалення:
1спробувати щось → перевірити результат → скоригувати → спробувати знову
Це і є атом. Один агент, один показник, цикл доти, доки не зійдеться.
Цикли мають відомий режим відмови: вони оптимізують саме те, що ви вимірюєте, і нічого іншого. Бот підтримки, налаштований швидко закривати заявки, закриватиме заявки швидко — поки задоволеність тихо падає. Цикл не бачить того, що за межами його власного показника. Це Закон Гудхарта у вашій архітектурі агентів.
Граф вирішує це за задумом. Замість одного циклу, що женеться за одним числом, ви будуєте мережу циклів, які спостерігають і виправляють один одного. Вихідні дані Вузла А живлять Вузол Б. Вузол В працює незалежно і перевіряє обох. Жоден окремий показник не керує всією системою — це робить структура.

Для систем агентів це означає один конкретний зсув: перестаньте писати одного агента, який робить все від початку до кінця. Спочатку спроектуйте форму роботи — що має відбутися перед чим, що може виконуватися одночасно, що насправді має чекати.
Розділ 2 - Вузли, ребра та тест, який їх розділяє
Граф має рівно два компоненти:
Вузол — одна одиниця роботи. Один агент, одне завдання, один вхід, один вихід.
Ребро — справжня залежність. Вхідні дані Вузла Б потребують вихідних даних Вузла А.
Помилка, якої припускаються майже всі: сприймати «а потім» як ребро за замовчуванням.
1«Прочитай цю кодову базу, а потім напиши список змін»2«Отримай сторінку ціноутворення, а потім підсумуй характеристики конкурентів»
Поставте одне запитання для кожного «а потім» у вашому робочому процесі:
Чи справді наступний крок читає вихідні дані попереднього кроку?
Так → справжнє ребро. Зберігайте послідовний порядок.
Ні → не ребро. Очікування марне. Виконуйте їх паралельно.
Якщо жодні дані не перетинають межу між двома завданнями, вони незалежні — і кожна незалежна пара, яку ви виконуєте послідовно, — це час виконання, який ви просто викидаєте.
Ось тест, застосований у коді:
1from dataclasses import dataclass23@dataclass4class TaskNode:5 id: str6 prompt: str7 depends_on: list[str] # Ідентифікатори вузлів, які цей насправді потребує89def has_real_edge(node_a: TaskNode, node_b: TaskNode) -> bool:10 """11 Основний тест графічної інженерії:12 чи дійсно prompt вузла 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- обробка помилок не витікає stack traces89Виконуй ці перевірки паралельно для всіх файлів маршрутів —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 - Де графи насправді ламаються
Графічна інженерія зазнає невдачі у трьох передбачуваних місцях. Знайте їх, перш ніж натрапите на них.
Колапс контексту. Розгалужте 1 000 вузлів і спробуйте передати всі 1 000 вихідних даних в один крок зведення — ви перевищите будь-яке контекстне вікно ще до початку синтезу.
Виправлення: шароване згортання. Групуйте вузли в партії по 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 # Фінальне зведення працює з підсумками, а не з 1 000 необроблених результатів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 Оркестратор2 |3 +--------+-------+-------+--------+4 v v v v v5 Вузол 1 Вузол 2 Вузол 3 ... Вузол N6 (паралельно, без ребер між жодним з них)7 | | | |8 +--------+-------+-------+-------+9 v10 Підсумок партії <- шарове згортання11 (групи по 30)12 v13 Фінальний звіт <- єдине справжнє ребро
Єдина робота оркестратора: розкласти завдання на вузли, визначити справжні ребра та відправити на виконання. Він сам не виконує жодної роботи — він малює граф.
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 року. Приклади коду є ілюстративними — адаптуйте обробку помилок, обмеження швидкості та логіку повторів до вашого продакшн-середовища перед розгортанням у масштабі.
Дякую за читання.





