Собрать команду агентов несложно. Сложно сделать так, чтобы она пережила первую неделю под нагрузкой.
Дальше разберём, как выбирать архитектурный шаблон команды по тому месту, в котором он предсказуемо ломается, где кончается польза от дробления задачи и почему изоляция исполнения повышает цену атаки, но ничего не гарантирует.
Дневник курса, урок 11. Отсюда пригодится разобранное раньше: когда команда агентов окупается (урок 8) — на этот вопрос отвечают до выбора шаблона, а не после; обрыв точности при росте каталога инструментов (урок 1) — из него растёт слабое место первого же шаблона в списке. Пост читается отдельно: все термины вводятся заново.
Содержание
- Что меняется при выходе в прод
- Паспорт паттерна: суть, домен, уязвимость, приём
- Иерархическая декомпозиция: арифметика ветвления
- Событийная архитектура: агенты на шине событий
- Гибридная оркестрация: вероятностный слой над детерминированным
- Режимы отказа и запасной путь
- Память долгоживущего агента: три слоя
- Изоляция исполнения: что реально даёт песочница
- Как части складываются в одну сборку
- Итог
- FAQ
- Источники
1. Что меняется при выходе в прод
На стенде мультиагентная система выглядит убедительно. Три агента с внятными ролями доводят подготовленный пример до конца, и лог читается как связный рассказ. В проде та же сборка ведёт себя иначе, причём падает она не там, где ждали. Рассуждает модель обычно нормально. Зато один агент выполняет шаг дважды, второй не видит результата первого, а третий десять минут уточняет детали у самого себя.
Это измерено. Систематический разбор MAST прошёл по 1600 с лишним размеченных трейсов с семи популярных open-source фреймворков и дал частоту отказа 41–86,7%. Разброс здесь говорит не меньше, чем крайние значения. Похожие по замыслу сборки падают то в четырёх случаях из десяти, то в девяти, и объясняется это тем, как они собраны, а не силой модели под ними.
Вторая цифра бьёт туда же. При неизменных задаче и модели смена топологии координации сдвигает оценку качества более чем на 30 пунктов по шкале самого бенчмарка и удваивает время работы (MSEval, 100 прогонов на 10 топологиях). Тридцать пунктов на шум замера не спишешь. Берутся они из того, что топология решает, кто из участников что увидит на входе. То, что в одной сборке доезжает до сборщика ответа исходным куском задачи, в другой приходит пересказом через двух посредников, и каждый пересказ что-нибудь теряет. То есть организация команды весит примерно столько же, сколько выбор модели. Новость неприятная. Модель выбирают осознанно, сравнивают по бенчмаркам и спорят о ней на ревью, а топология обычно складывается сама собой, из того, как было удобнее писать код.
Нужна ли тут вообще команда — отдельная тема, и отвечать на этот вопрос надо до всего остального. По данным Anthropic, агент расходует примерно вчетверо больше токенов, чем чат, а мультиагентная система — примерно в пятнадцать раз больше; расход токенов сам по себе объясняет 80% разброса результата. Последнюю формулировку стоит прочитать буквально, потому что речь идёт о доле дисперсии. Лаборатория сопоставила оценки своих прогонов с потраченными токенами, и четыре пятых различий в качестве легли на одну эту переменную. На всё остальное вместе, включая выбор модели, устройство промптов и топологию, осталась пятая часть.
Откуда берётся такая кратность? У каждого участника свой системный промпт, своя история и своя копия куска общего контекста. Документ, который чат прочитал один раз, команда из пяти агентов прочитает пять раз, а сверху лягут сообщения, которыми они координируются между собой.
Платить за это иногда стоит. Связка orchestrator-worker у той же лаборатории (Opus 4 ведущим, Sonnet 4 исполнителями) обошла одиночную модель на 90,2% во внутреннем тесте на исследовательских задачах. Проценты здесь относительные. Балл связки оказался почти вдвое выше балла той же ведущей модели, работавшей в одиночку, и это не доля выигранных сравнений и не пункты какой-либо шкалы. Тест внутренний, а задачи исследовательские, то есть такие, где поиск легко раскидать по независимым источникам и потом сложить. Дальше считаем, что решение принято и команда уже собрана.
2. Паспорт паттерна: суть, домен, уязвимость, приём
Выбирают шаблон чаще всего так. Кто-то приносит схему с квадратиками и стрелками, схема выглядит логично, её собирают. Через месяц сборка начинает ломаться ровно в том месте, где у этого шаблона есть известное слабое место, про которое на схеме ничего не было нарисовано.
Поэтому нам полезнее описывать шаблон четвёркой «суть — домен — уязвимость — приём против неё», чем картинкой. Решающий пункт здесь третий. Паттерн выбирают не по красоте схемы, а по тому, готовы ли вы платить за его слабое место.
Один агент с набором инструментов. Домен — простые ассистенты. Уязвимость — эффект свалки. Точность падает по гиперболе с ростом каталога, а перелом наступает примерно после семи инструментов, и дальше не «чуть хуже», а заметно хуже. И форма кривой, и сам порог остаются эмпирическим ориентиром из замеров на каталогах разного размера (обрыв точности разобран отдельно), а не законом природы. Почему кривая вообще заваливается? Дело в том, как модель выбирает инструмент: она читает описания и берёт подходящее по смыслу. Пока инструментов пять, описания различаются очевидно; когда их пятнадцать, в каталоге живут cancel_order, refund_order и revoke_subscription, и различие между ними держится на одном прилагательном в docstring. Выбор превращается в бросок монеты. Уговоры в промпте («внимательно читай описания») тут не помогают, потому что дело не в старательности модели, а в том, что различающего признака в описаниях больше нет. Лечится дроблением на специализированных агентов, у каждого из которых свой короткий каталог.
Последовательный пайплайн. Каждый агент решает одну задачу, выход предыдущего становится входом следующего. Домен — комплаенс, скоринг, юридические проверки. Уязвимость — накопление ошибок, когда ложный контекст на старте отравляет всё, что ниже. Допустим, извлекающий агент вытащил из справки сумму дохода и потерял валюту. Дальше по цепочке никто уже не сомневается. Скоринг честно считает по числу, проверяющий честно применяет пороги, и на выходе получается аккуратно оформленное неверное решение. Приём — детерминированные парсеры на стыках. Pydantic-схема срабатывает до вызова следующей модели, а не в конце, и разница практическая: проверка на стыке говорит, какой именно шаг сломался, а проверка на выходе сообщает только то, что результат негодный. Шаблон недооценён. В замерах MSEval именно структурированные пайплайны сходятся быстрее всех и с лучшим качеством, а тяжёлый управляющий слой качество ухудшает.
Координатор с динамической маршрутизацией. Диспетчер раскидывает запросы по изолированным исполнителям, у каждого чистый системный промпт до 500 токенов. Ломается такая схема предсказуемо, потому что координатор остаётся единой точкой отказа: ошибка классификации уводит пользователя в тупиковую ветку. Тупик при этом выглядит прилично. Никакой ошибки 500, вежливый агент бодро отвечает не по теме, а пользователь ещё несколько ходов пытается объяснить ему свою задачу. Приёмы известны. На маршрутизацию не тратят тяжёлую генерацию, а берут лёгкую модель со строгим JSON или семантический поиск по эмбеддингам, и обязательно ставят температуру в ноль. Маршрутизация — это классификация с закрытым списком ответов, разнообразие на выходе там ничего не улучшает. Маршрутизатор не должен творить.
Рефлексия с обратной связью от среды. Агент пишет код, среда его прогоняет, ошибка возвращается в следующую попытку. Оценку даёт не другая модель, а компилятор, линтер, тесты; лог ошибок и удачных исправлений копится и подаётся в следующую попытку. Модель-судья тем и хуже, что ошибается заодно с генератором и подтверждает неработающий код; компилятор либо соберёт, либо нет, и уговорить его нельзя. Домен — конвейеры переноса данных (ETL: извлечь, преобразовать, загрузить), генерация SQL под меняющуюся схему. Уязвимость тут не про качество. Без песочницы паттерн означает исполнение произвольного сгенерированного кода на хосте.
Параллельная агрегация. На вид она ничего не стоит, раз независимые проверки запускаются разом, а результат потом синтезируется. Упирается всё в пропускную способность инференса, потому что независимы здесь агенты, а железо под ними общее. Пять проверок, каждая по десять секунд, на бумаге дают десять секунд вместо пятидесяти; на одном эндпоинте с ограниченным пулом воркеров те же пять запросов встают в очередь и возвращают исходные полсотни, только теперь ещё и с риском тайм-аута. Лимит одновременных запросов задают в инфраструктуре, а не в коде агента. Иначе выигрыш по времени съедает очередь в пуле.
Сведём пять шаблонов в одну рамку:
| Шаблон | Домен | Слабое место | Приём против него |
|---|---|---|---|
| Один агент с инструментами | простые ассистенты | эффект свалки: перелом после ~7 инструментов | разбить на агентов с коротким каталогом |
| Последовательный пайплайн | комплаенс, скоринг, юрпроверки | накопление ошибок: ложный контекст отравляет всё ниже | Pydantic-схема на стыке, до вызова следующей модели |
| Координатор с маршрутизацией | разнородные запросы | единая точка отказа: ошибка классификации уводит в тупик | лёгкая модель со строгим JSON, температура 0 |
| Рефлексия с обратной связью | код, тесты, линтеры | исполнение сгенерированного кода на хосте | песочница; exec() в процессе приложения — никогда |
| Параллельная агрегация | независимые проверки | общее железо: пять по десять секунд — не десять | лимит конкурентности в инфраструктуре, не в коде агента |
Правая графа заполнена однотипно, и это не совпадение. Против каждой уязвимости стоит детерминированный ограничитель в коде: счётчик, схема, нулевая температура, лимит очереди, песочница. Промпта в этой графе нет ни разу. Уговорить вероятностную модель не ошибаться нельзя. Обрезать ей пространство ошибки кодом — можно.
3. Иерархическая декомпозиция: арифметика ветвления
Параллельная агрегация делит работу между равными исполнителями, но делит её заранее и руками разработчика. Следующий шаг напрашивается сам. Пусть задачу разбирает не автор сборки, а сам агент, тогда команда справится и с тем, чего вы не предусмотрели. Так получается иерархия, и это самый дорогой шаблон из перечисленных.
Дальше всё пойдёт на одном примере — типовая виртуальная команда из трёх ролей. PM-агент разбирает бизнес-задачу и выдаёт план с критериями приёмки. Dev-агент читает план, пишет код и запускает его. QA-агент пишет тест-кейсы, прогоняет их и возвращает баг-репорт; круг замыкается, когда QA выставляет status: "passed". Иерархия здесь заводится сама собой — план PM-агента и есть дерево подзадач, и дробит его модель, а не человек.
Топология выглядит как Root Planner → Workers → Synthesizer. Планировщик дробит задачу, воркеры не знают об общей цели, синтезатор собирает ответ. Опасна она рекурсивностью, потому что воркер, получив слишком крупную подзадачу, сам становится планировщиком. Тут никто не ошибается. У воркера тот же инструмент делегирования, задача в один проход не решается, и он поступает единственно разумным для себя образом — дробит её дальше. Один запуск порождает 20–30 внутренних вызовов модели, и это порядок величины из практики, а не константа.
глубина дерево при ветвлении по 5 узлов
D=0 [planner] 1
D=1 [w] [w] [w] [w] [w] 5
D=2 [.....] x5 25
D=3 [.........................] x5 125
лимит глубины ставит оркестратор, не промпт:
LangGraph 1.2.10 recursion_limit = 10007 -> не ограничивает
OpenAI Agents SDK max_turns = 10 -> витки агента,
а не уровни делегированияНасколько глубоко имеет смысл дробить? Практическая эвристика — не больше трёх уровней, и держит глубину не промпт («не углубляйся сильно»), а счётчик в коде оркестратора. Причина простая: модель не умеет надёжно оценивать собственную глубину рекурсии. Воркер третьего уровня видит свою подзадачу и родительскую формулировку, но не видит, сколько раз задачу дробили до него. Счётчик в оркестраторе видит, потому что он один на всё дерево. Само число 3 за пределами этого материала не встречается, зато направление подтверждают измерения. OrchBench формулирует вывод так: «сохранить критичную для задачи информацию важнее, чем нарастить число агентов, а польза от параллелизма тает по мере накопления координационных отказов».
Дефолты SDK весомее любой эвристики. Историческая планка LangGraph в 25 итераций больше не действует, актуальный релиз ставит DEFAULT_RECURSION_LIMIT в 10 007 и выносит порог в переменную окружения. Такое число работает предохранителем от бесконечного цикла, а не бюджетом на задачу. При цене нормального запуска в 20–30 вызовов оно сработает далеко за пределами всего, что вы готовы оплатить. У OpenAI Agents SDK лимит есть, однако считает он витки одного агента, а не уровни вложенности делегирования. Каждый запуск получает свои десять витков независимо от того, кто его породил, так что глубину дерева этот счётчик не ограничивает вовсе. Безопасного дефолта по глубине не даёт ни один из двух фреймворков. К лимиту добавляются тайм-ауты на каждый узел. Без них зависший воркер держит всё дерево, а планировщик не знает, ждать ему или считать ветку провалившейся, и по умолчанию ждёт.
Есть и отдельная контрпозиция от команды Devin. Архитектуру «разбить → раздать субагентам → склеить» там называют хрупкой, потому что субагенты принимают несогласованные неявные решения, не видя трейса друг друга. Каждый достраивает недостающий контекст сам, достраивают они по-разному, а несовпадение вылезает уже на сборке, когда куски не стыкуются. Рецепт оттуда простой. Субагент отвечает на хорошо поставленный вопрос и не пишет кусок системы.
4. Событийная архитектура: агенты на шине событий
Все предыдущие шаблоны запускаются запросом. Кто-то попросил, команда пошла выполнять. Часть задач так не устроена. Никто не «просит» заметить мошенническую транзакцию: момент, когда это надо сделать, задаёт поток событий, а не пользователь. Ставить над таким потоком координатора бессмысленно, ведь очередь запросов, которую он должен разбирать, пуста.
Главного агента здесь нет вообще. Реактивные агенты подписаны на шину событий (Kafka, RabbitMQ), каждый слушает свои типы событий и сам решает, вмешиваться или нет, а результат его работы тоже становится событием. Выигрыш понятен. Единой точки отказа больше нет, а новый агент добавляется подпиской, а не переписыванием графа, и цена появления нового участника перестаёт зависеть от того, сколько их уже есть. Домен узкий — мониторинг безопасности в реальном времени и выявление мошеннических транзакций, то есть задачи, где момент срабатывания неизвестен заранее.
Что стоит за красивой формулировкой «агент сам решает, вмешиваться или нет»? Если понимать её буквально, дорого. Решение о том, интересно ли событие, принимают до модели, фильтром по типу и атрибутам на стороне подписки. Иначе каждое движение в потоке превращается в вызов модели, и стоимость начинает зависеть от объёма трафика, а не от числа реальных инцидентов.
Два требования жёсткие, без них паттерн разваливается.
- Идемпотентность — повторная обработка того же события не должна менять результат. Шина гарантирует доставку «хотя бы один раз», и повтор придёт обязательно: подтверждение обработки теряется уже после того, как обработка прошла, отправитель об этом не знает и шлёт сообщение заново. Если обработчик списывает деньги без ключа идемпотентности, дубликат превращается в инцидент.
- Карантинная очередь (dead-letter queue) — отдельная очередь для событий, на которых обработка сломалась, чаще всего на парсинге ответа модели. Без неё битое событие либо теряется молча, либо бесконечно возвращается в основной поток: брокер отдаёт его снова, обработчик снова падает, и конвейер встаёт из-за одного сообщения.
Слабое место — потеря трассировки. В синхронной цепочке идентификатор трейса едет HTTP-заголовком сам собой. В шине его никто не прокидывает автоматически, и traceparent приходится класть в метаданные сообщения и восстанавливать на стороне подписчика руками, в каждом агенте. Пренебрегли — и разбор инцидента превращается в сопоставление меток времени из разных логов. Связи между «пришло событие» и «агент что-то сделал» в данных нет, её приходится угадывать.
5. Гибридная оркестрация: вероятностный слой над детерминированным
Вопрос «это делает агент или код» обычно возникает на конкретной функции (списать деньги, выдать доступ, отправить документ), а обсуждают его так, будто ответ должен быть один на всю систему. Отсюда и тупик. Сторонники агента приводят гибкость, сторонники кода — воспроизводимость, и оба правы про разные части задачи.
Гибридная оркестрация снимает у нас сам спор. Вероятностный слой садится поверх детерминированного, и у каждого своя зона. Всё, что трогает деньги, доступы и внешние API, выполняет строгий код; агентам остаётся когнитивная работа.
вероятностный слой интерпретация запроса, выбор стратегии,
(LLM-агенты) подготовка данных, формулировка ответа
─────────────────────────────────────────────────────────────
контракт типизированный объект (Pydantic),
валидация до передачи вниз
─────────────────────────────────────────────────────────────
детерминированный транзакции, права, вызовы API,
слой (код, автомат) переходы состояний, аудитВозьмём возврат средств. Агент читает переписку, понимает, что клиент просит вернуть деньги за конкретный заказ, и отдаёт объект: номер заказа, сумма, причина, признак «нужно подтверждение оператора». Больше он не делает ничего. Проверку сроков, прав, остатка, вызов платёжного API и запись в аудит выполняет код, который вёл бы себя точно так же, приди этот объект из веб-формы. Разделение окупается при разборе инцидента. На вопрос «почему ушли деньги» отвечают валидированный вход и конкретная ветка кода, а не «так решила модель».
Нижний слой — конечный автомат (FSM, finite state machine: фиксированный набор состояний и разрешённых переходов). Плата за такой детерминизм приходит через год, и автомат превращается в спагетти. Бизнес-правила меняются, граф обрастает ветками, и разобраться в нём уже нельзя. Хуже того, часть логики живёт в промптах, часть в коде, и граница между ними размывается. Приём — вынести граф переходов в декларативную конфигурацию, отдельную от исполняемого кода:
states:
triage: { "on": { classified: risk_check, unclear: human_review } }
risk_check: { "on": { low: auto_reply, high: human_review } }
human_review: { terminal: true }
Тогда правка бизнес-правила проходит ревью и версионируется отдельно от логики исполнения, а сам граф можно показать тому, кто эти правила придумывает: он читается без знания языка. Второе место, где стоит подстраховаться на стыках, — контракты инструментов. Плоские схемы аргументов вместо вложенных объектов (модели до 14B параметров ошибаются на глубокой вложенности), явные type, enum и регулярные выражения. Битый JSON чинит библиотека восстановления, а не повторный запрос к модели, и так дешевле: перезапрос стоит полного прогона контекста и не даёт гарантии, что во второй раз скобка закроется.
6. Режимы отказа и запасной путь
Дежурный смотрит на дашборд и не видит ничего тревожного. Код 200, время ответа в норме, ошибок нет. Агент при этом уже неделю выдаёт негодный результат. Классический мониторинг измеряет доступность и задержку, а агент отвечает вовремя и корректным HTTP. Неверно только содержание ответа, и заметить это можно, лишь прочитав его.
Соберём короткий чек-лист из трёх сигнатур, на которые вешают алерты. Бесконечные петли: условия выхода нет, агент ходит по кругу, и замечают это обычно по расходу токенов, а не по метрике. Порча памяти начинается с невалидного JSON, который попал в долговременное состояние, и теперь каждый следующий запуск читает его и либо падает, либо строит рассуждение на мусоре. Каскадный коллапс растёт из одного неверного шага, отравившего контекст: дальше вся генерация идёт от ложной предпосылки и выглядит безупречно логичной. Ни одна из трёх сигнатур не видна в привычных метриках, зато все три видны в производных — число шагов на сессию, токены на задачу, доля задач, закрытых без артефакта.
Оценку «на эти три приходится около 80% сбоев» стоит держать как эвристику приоритизации алертов, а не как измеренный факт. Ходит она по производственным разборам и презентациям, независимого подтверждения у неё нет, и первоисточник назвать некому. Академическая картина шире и устроена иначе. MAST раскладывает поведение сборок на 14 режимов отказа в трёх категориях: дефекты проектирования, рассогласование между агентами, провалы верификации. Универсальную пропорцию работа прямо отрицает, потому что распределение отказов заметно различается между системами и отражает их архитектуру. Так что чек-лист берите, а долю проверяйте на своих трейсах.
Лимиты к этому месту расставлены везде: потолок глубины в оркестраторе, потолок кругов у цикла рефлексии, лимит одновременных запросов в инфраструктуре. А что происходит в секунду, когда лимит сработал? Вопрос звучит формально ровно до того, как заглянешь в код: там обычно raise или молчаливый возврат последнего черновика, и пользователь получает недоделанную работу без единого признака, что она недоделана. Выход устроен иначе, и у нашей команды из трёх ролей он выглядит так. После каждого круга оркестратор считает взвешенную оценку решения: Q = w_pm · S_pm + w_qa · S_qa, где S — оценка от 0 до 1, которую агент поставил результату по своей части, а w — вес его экспертности, зашитый в архитектуру. Веса задаёт разработчик, а не модель: на соответствии требованиям больше весит PM, на качестве кода — QA. Если за отведённое число кругов Q так и не перевалил порог, цикл прерывается и задача уходит человеку — вместе с полным логом разногласий: что предложил Dev, что забраковал QA и на каком именно критерии они разошлись. Без лога эскалация бесполезна. Человек получает задачу, на которую агенты потратили все отведённые круги, и начинает разбираться с чистого листа.
Отдельная история — деградация внешнего провайдера. Ретраи спасают от всплеска, но не от долгой недоступности. При затяжном отказе повтор только нагружает и без того упавшую сторону и держит ваши воркеры занятыми ожиданием. Там ставят предохранитель (circuit breaker), который перестаёт слать запросы в упавший сервис.
Closed ──── >15% ошибок 5xx за 30 с ────> Open <──┐
^ │ │
│ тайм-аут │ провал пробы
успех v │
└──────────── проба ──────────────── Half-Open ─┘
Open -> трафик уходит на запасной путь (локальный vLLM)Всё это опирается на трассировку. Консольные логи бесполезны, когда сессия длится пятнадцать минут и состоит из полусотни автономных шагов. Нам понадобятся сквозные идентификаторы сессии и трейса, точный промпт каждого узла, сырые аргументы вызовов и расход токенов на шаг. Промпт пишут именно целиком, а не ссылкой на шаблон, потому что собирается он на лету из шаблона, памяти и результатов предыдущих шагов, и по репозиторию восстановить, что модель увидела на входе, уже не выйдет. Без такого следа предохранитель нечем питать: долю ошибок и порог срабатывания берут именно оттуда. Как этот след ложится на состояние графа — тема оркестрации графом. Закладывать трассировку надо сразу, потому что дописать её в живую систему выйдет дороже.
7. Память долгоживущего агента: три слоя
Агент без памяти каждую сессию начинает с чистого листа. Заново выясняет то, что выяснял вчера, заново предлагает решение, которое пользователь уже отверг, заново перечитывает те же документы. Оркестрация без памяти — это деньги на ветер и лишние ретраи. У долгоживущего агента память ложится слоями по роду знания, и дисциплина у каждого слоя своя.
| Слой | Что хранит | Отвечает на вопрос | Дисциплина очистки |
|---|---|---|---|
| Эпизодическая (векторный поиск) | прошлые сессии и диалоги | было ли уже такое | чистится по времени |
| Семантическая (граф) | факты и связи сущностей | что мы точно знаем | не чистится, только растёт |
| Процедурная (системные промпты) | правила и ограничения работы | как мы это делаем | меняется через ревью |
Дисциплины разные не для симметрии таблицы. Прошлогодний диалог про отменённый тариф скорее собьёт агента, чем поможет, потому что эпизодический слой стареет вместе с обстоятельствами. Факт «клиент работает в юрлице X» от времени не портится, его отменяет только другой факт, поэтому семантический слой растёт. Правила работы меняют поведение агента сразу для всех пользователей, и такое изменение проходит через ревью, как любой деплой.
Связи вроде User_102 → предпочитает → Python_Async живут в графе не для красоты. По вектору связь не ищется: эмбеддинг найдёт похожий текст, а вопрос «что именно предпочитает пользователь 102» решается обходом ребра, а не поиском ближайшего. Релевантность при извлечении считают не по одной близости, а по сумме трёх слагаемых — векторное сходство, экспоненциальное затухание по времени и частота обращений. Забывание встроено в формулу ранжирования, а не в удаление. Предпочтение полугодичной давности формально останется в базе, но перестанет попадать в контекст, и это обратимо, в отличие от вычищенных строк. Поведение настраивают соотношением коэффициентов: подняли вес свежести — получили агента, живущего последними неделями.
Посмотрим, как три слоя работают вместе на живой задаче. PM-агенту приходит требование добавить в сервис фоновую выгрузку отчётов. Эпизодический слой поднимает прошлый заход на похожую выгрузку и то, чем он кончился — упёрлись в тайм-аут. Семантический достаёт связь User_102 → предпочитает → Python_Async про разработчика, который будет принимать работу. Процедурный добавляет правило компании не тащить библиотеки вне утверждённого списка. Ни один слой не заменяет другие: уберём эпизодический — команда второй раз наступит на тот же тайм-аут, уберём процедурный — Dev-агент напишет красивое и запрещённое.
Поверх всех трёх слоёв действует жёсткое ограничение. Персональные данные, ключи и временные переменные в долговременную память не пишутся. Фильтр здесь детерминированный и стоит до записи в хранилище, а не при чтении. Иначе данные уже лежат в базе и уедут в бэкап, в реплику и в аналитическую выгрузку, где никакого фильтра на чтении нет.
8. Изоляция исполнения: что реально даёт песочница
Рефлексия с обратной связью от среды, доведённая до практики, означает ровно одно. Модель пишет код, и этот код кто-то запускает. В нашей команде запускает Dev-агент: он прогоняет собственный скрипт, чтобы отдать QA не текст, а лог выполнения.
В уроках 5 и 9 песочница стояла последним барьером в модели угроз одного агента — действующего в чужом интерфейсе или читающего недоверенный текст. Враждебная инструкция приходит снаружи, фильтры её пропускают, изоляция ограничивает ущерб от того, что уже случилось. Здесь то же средство отвечает на другой вопрос, и злоумышленника в кадре нет вовсе. Исполнение чужого кода тут не последствие атаки, а свойство выбранного шаблона. Цикл рефлексии на каждой итерации порождает новый скрипт, и запускать его придётся десятки раз за одну задачу, даже когда всё идёт как задумано. Изоляция из реакции на инцидент превращается в условие, без которого паттерн не собирается.
Отсюда правило без исключений: никогда не запускать exec() в хост-процессе приложения. Там лежат переменные окружения, ключи и открытые соединения с базой, то есть всё, ради чего к вам и пришли бы. Дальше выбор идёт по градиенту от контейнера к отдельной виртуальной машине, и нам важно понять, где на этом градиенте встать.
контейнер ──> gVisor ──> микро-VM
общее ядро перехват своё ядро
хоста системных через KVM
вызовов
────────────────────────────────────────────>
изоляция сильнее, накладные расходы выше
общий page cache протекает на всех трёх уровняхЗамер 2026 года на одном стенде подтверждает градиент, но бинарной гарантии там нет. Виртуализация добавляет задержку и шум, однако страничный кеш остаётся общим на всех трёх уровнях, а он измерим: по времени доступа сосед по железу понимает, что уже было прочитано, и делает выводы о чужой работе. Утечка меняет форму, а зависимость от общего железа и состояния кеша никуда не девается. Формулировку «выйти за периметр невозможно» стоит считать рекламной, потому что речь идёт про стоимость атаки. Чем именно платят за каждую ступень этого градиента — холодным стартом, временем до готовности и счётом за простаивающее железо — разбирает пост про инфраструктуру ИИ-агента (урок 24).
Практика подтверждает, что оба конца градиента живые. Modal исполняет сгенерированный моделями код на gVisor, той же технологии, что лежит под Google Cloud Run и GKE. Anthropic для песочницы своего кодинг-агента обошлась без микро-VM и взяла средства операционной системы: bubblewrap на Linux и WSL2, Seatbelt на macOS; сетевой трафик идёт через прокси. Линия микро-VM при этом никуда не делась и уместна там, где песочница живёт долго и права внутри широкие. Для цикла рефлексии картина обратная. Короткое исполнение повторяется десятки раз за одну задачу, и секунда на старт изолированной среды складывается в минуты ожидания. Раз решают накладные расходы на запуск, выигрывает WASM-песочница, исполняющая Python внутри WebAssembly: среда исполнения физически не видит файловой системы и сети без явного проброса.
Поверх изоляции ставят ограничения на инструменты, которые ходят в чужие системы: доступ к базе только через учётные записи с правами на чтение, тайм-аут и лимит вывода порядка полусотни строк, любые операции изменения данных — через подтверждение оператором. Лимит вывода тут бережёт не канал, а контекст. Результат SELECT * по крупной таблице уедет прямо в окно модели и вытеснит оттуда всё, ради чего запрос затевался.
9. Как части складываются в одну сборку
Разделы выше разбирали части порознь — топологию, глубину дерева, память, изоляцию. Наша команда из трёх ролей, собранная целиком, выглядит так.
[задача] ─► Orchestration Core ┌─────────────────────┐
│ PM-агент ◄──────────► │ память: эпизоды, │
│ (план + критерии) │ граф фактов, правила│
▼ └─────────────────────┘
Dev-агент ──► песочница ──► лог выполнения ──┐
▲ одноразовый контейнер, │
│ сеть выключена ▼
└───────── баг-репорт ────────────── QA-агент
│
Q = w_pm·S_pm + w_qa·S_qa ◄──┘
│
┌───────────┴───────────┐
Q ≥ порог Q < порог
результат наружу человек + лог разногласийQ решает, отдать результат или прервать цикл и позвать человека с логом разногласий.Перед выпуском такой сборки полезен короткий чек-лист, и три пункта в нём весят больше остальных. Жёсткий потолок кругов и токенов — без него цикл рефлексии оплачивается по факту, а факт узнаётся из счёта в конце месяца. Подтверждение человеком на любой транзакции — не потому, что модель глупа, а потому, что списание денег необратимо, а Q ≥ порог вероятностно. И полный трейс действий: без него разбор зациклившегося прогона превращается в сопоставление меток времени, а с ним — в один запрос по идентификатору. Остальное из чек-листа (начинать с одного агента, дробить нечитаемый промпт на двух, обращаться с агентами как с микросервисами, сжимать контекст, валидировать вход и выход схемами, фильтровать долговременную память, гонять систему на настоящих ошибках API) — обычная инженерная гигиена. Без неё сборка живёт, просто хуже. Без первых трёх — не живёт.
Итог
- Уязвимость шаблона — главный критерий выбора: эффект свалки у одиночного агента, накопление ошибок у пайплайна, единая точка отказа у координатора, риск исполнения кода у рефлексии.
- Глубину декомпозиции держит счётчик в оркестраторе; промптом это не лечится. Дефолты фреймворков либо запредельны (10 007 у LangGraph), либо меряют не то (витки агента у OpenAI Agents SDK).
- Событийная модель покупает отсутствие единой точки отказа ценой ручного проброса трассировки; идемпотентность и карантинная очередь — не опции.
- Всё, что трогает деньги и права, живёт в детерминированном слое, а граф переходов — в конфиге, иначе через год машина состояний нечитаема.
- Сработавший лимит — не исключение, а ветка сценария: взвешенная оценка ниже порога прерывает цикл и отправляет задачу человеку вместе с логом разногласий.
- Изоляция поднимает цену атаки, но гарантий не даёт. Долю «80% сбоев на три сигнатуры» проверяйте на своих трейсах: распределение отказов зависит от архитектуры системы.
Общий знаменатель у всех этих приёмов один. Против вероятностной ошибки ставят детерминированный ограничитель в коде — счётчик глубины, схему на стыке, нулевую температуру маршрутизатора, порог консенсуса, фильтр до записи в память, песочницу вокруг чужого кода. Промптом ни один из них не заменяется, и в этом же смысле стоит читать формулировку Cognition: субагент отвечает на хорошо поставленный вопрос и не пишет кусок системы. Команда, собранная по такому правилу, первую неделю под нагрузкой переживает — не потому, что модели под ней умнее, а потому, что у каждой её ошибки заранее выставлен предел.
FAQ
Как выбрать архитектурный паттерн для мультиагентной системы?
По уязвимости, а не по схеме. У каждого шаблона есть известное слабое место: у одиночного агента с инструментами — падение точности после семи инструментов, у последовательного пайплайна — накопление ошибок, у координатора — единая точка отказа, у иерархии — стоимость и задержка ответа. Выбирайте тот, чью цену вы готовы платить и чей приём защиты можете реализовать.
Насколько глубоко можно дробить задачу на подзадачи?
Практический ориентир — не больше трёх уровней вложенности, с лимитом в коде оркестратора и тайм-аутами на каждый узел. Один запуск иерархии обходится в 20–30 вызовов модели: оценка из практики, не константа. Уже при ветвлении по пять третий уровень даёт 125 листьев. Измерения OrchBench показывают, что польза от параллелизма тает по мере накопления координационных отказов, поэтому сохранение критичной информации важнее числа агентов.
Что обязательно нужно для событийной архитектуры агентов?
Идемпотентная обработка и карантинная очередь для сбойных событий. Шина гарантирует доставку «хотя бы один раз», поэтому дубликат придёт обязательно, а событие, на котором сломался парсинг ответа модели, должно уходить в отдельную очередь и не крутиться в основном потоке. Третье, о чём забывают, — идентификатор трейса нужно класть в метаданные сообщения вручную в каждом агенте, поскольку шина, в отличие от HTTP, не переносит его сама.
Что должен делать код, а не агент?
Всё, что трогает деньги, доступы, права и внешние транзакции. Агент интерпретирует запрос и возвращает валидированный типизированный объект с параметрами, а выполняет операцию детерминированный слой, который отработал бы одинаково и на объекте из веб-формы. Это даёт воспроизводимость и аудит. На вопрос регулятора «почему система так решила» ответ «так решила модель» не работает.
Чем заменить Docker для исполнения сгенерированного агентом кода?
Однозначного ответа нет, есть градиент: контейнер, gVisor с перехватом системных вызовов, микро-VM со своим ядром. Для коротких повторяющихся исполнений выигрывает лёгкий вариант, вплоть до WASM-песочницы, потому что там решают накладные расходы на старт; для долгих сессий с широкими правами — микро-VM. При этом ни один уровень не снимает побочный канал через общий страничный кеш, так что изоляция снижает вероятность и повышает цену атаки, но не даёт гарантии.
Когда стоит ставить предохранитель на вызовы модели?
Когда у вас есть запасной путь. Типовой порог срабатывания — свыше 15% ошибок 5xx за 30 секунд, дальше цепь размыкается и после тайм-аута пропускает пробный запрос. Без запасного пути (например, на локальный инференс) размыкание цепи просто превращает недоступность провайдера в ошибки вашего сервиса. Запасной путь обычно слабее основного по качеству, и это приемлемо: цель — деградировать, а не останавливаться.
Источники
- Anthropic — How we built our multi-agent research system — кратность расхода токенов, доля дисперсии и эффект связки orchestrator-worker.
- Why Do Multi-Agent LLM Systems Fail? (MAST) — arXiv:2503.13657 — 14 режимов отказа в трёх категориях и частота отказа 41–86,7% на семи фреймворках.
- OrchBench — arXiv:2607.25656 — оценка планов оркестрации симуляцией: предел полезного ветвления и накопление координационных отказов.
- MSEval — arXiv:2607.27877 — топология координации против качества и времени: сдвиг оценки на 30+ пунктов при неизменной модели.
- LangGraph
_config.py· OpenAI Agents SDKrun_config.py— дефолтные лимиты рекурсии и витков — как они есть в исходниках. - gVisor — Security · Modal — Security — механика перехвата системных вызовов и её эксплуатация на агентных нагрузках.
- Claude Code sandboxing — изоляция файловой системы и сети средствами ОС вместо микро-VM.
- Isolation Failure From Shared Storage — arXiv:2607.17518 — замер градиента изоляции на одном стенде и общий побочный канал через страничный кеш.
- Cognition — Don't Build Multi-Agents — контрпозиция к иерархической декомпозиции.
Числовые ориентиры в тексте получены на конкретных корпусах и профилях нагрузки: кратность токенов лаборатория мерила на своём наборе задач и своём поколении моделей, частоту отказов считали на open-source фреймворках, а пороги предохранителя и лимит глубины взяты из практики — калибруйте их на своих трейсах.