Ранас Мукминов – Оркестрация ИИ-агентов. Claude Opus 4.7 (страница 85)
Есть ли известный адресат | да, известен | нет, безадресно | нет, читатель находит сам
Нужно ли хранить переданное | нет, эфемерно | состояние и так хранится | да, устойчиво
Изменяется ли переданное после передачи | нет | да, совместно | нет (новая версия — новый объект)
Сколько потребителей | один или известная группа | заранее неизвестно | много, в разное время
Известен ли порядок взаимодействия | да | нет, выводится из состояния | не важно (артефакт ждёт)
Нужна ли точка контроля человеком | на стыке | трудно | да, естественно
Какой класс отказов приемлем | потеря/дубль/порядок | гонки/отравление | висячие ссылки/версии
Практические правила выбора, вытекающие из таблицы:
Если у передачи есть известный адресат и переданное не нужно хранить — это сообщение. Не превращайте адресную эфемерную передачу в общее состояние; вы лишь добавите гонки.
Если результат устойчив, может понадобиться нескольким потребителям в разное время и должен быть доступен для проверки — это артефакт. Не передавайте крупный устойчивый результат сообщением; транспорт для этого не предназначен, а проверяемость теряется.
Если координация безадресна, порядок шагов неизвестен и решение строится многими агентами инкрементально — это общая память, и притом обоснованная. Но прежде чем выбрать её, убедитесь, что задача действительно безадресна; общая память — канал, который надо доказать, а не взять по умолчанию.
Если общая память необходима, рассмотрите её реализацию через журнал неизменяемых артефактов вместо единого изменяемого объекта: это снимает затирание и даёт происхождение ценой роста и необходимости определять актуальную версию.
При сомнении выбирайте канал с наиболее локальным классом отказов. Сообщение и артефакт затрагивают конкретную связь; общая память затрагивает всех, кто на состояние опирается. На прочих равных предпочтительна изоляция отказа, а не его глобальность.
Эти правила задают основу для остальной части IV. Поскольку канал сообщений несёт наибольшую долю адресной координации в типовом рое, главы 24–30 разбирают преимущественно его: синхронность и латентность (глава 24), формат и схему (глава 25), межагентные протоколы (глава 26), маршрутизацию (глава 27), события и подписки (глава 28), семантику доставки (глава 29), failure modes (глава 30). Общая память как канал координации детально разобрана для blackboard (см. главу 11) и для распределённого состояния в части VII; артефакты как форма результата возникают в темах сборки (см. главу 37), изоляции областей (см. главу 55) и контроля человеком (см. часть XIII). Эта глава задала рамку, в которой все эти разборы — частные случаи выбора между тремя каналами.
— Между агентами есть ровно три способа передать содержимое: адресное сообщение со сменой владения и эфемерной природой, чтение и запись общего изменяемого состояния и обмен устойчивым именованным артефактом. Эти три канала исчерпывают пространство и различаются не транспортом, а владением данными, временем их жизни и тем, кто отвечает за согласованность.
— Сообщение — единственный канал без общего изменяемого состояния между сторонами: оно даёт изоляцию отказов и естественную точку контроля доверия на стыке агентов. Его отказы — потеря, дублирование, переупорядочивание, искажение смысла — локальны по затронутым сторонам.
— Общая память покупает развязку между агентами ценой переноса всей координации в совместно изменяемое состояние. Её отказы — гонки и потеря обновлений, несогласованное чтение, отравление, неограниченный рост — глобальны: затрагивают всех, кто на состояние опирается. Это канал, который надо обосновать, а не выбрать по умолчанию.
— Артефакт занимает промежуток: неизменяемость снимка от сообщения плюс устойчивость и безадресность от общей памяти. Атомарность появления и неизменность содержания избавляют читателя от несогласованного чтения; его отказы — висячие ссылки, рассинхрон версий, гонка видимости, накопление — временны́е и в основном локальные.
— Каналы комбинируются: типична связка «сообщение-уведомление плюс артефакт-данные», полезна реализация общей памяти через журнал неизменяемых артефактов. Антипаттерны — имитация общей памяти россыпью сообщений об изменениях (наследует худшее обоих каналов) и общая память там, где фактически один писатель и один читатель (сообщение, переодетое в гонки).
— Канал выбирается на каждую связь отдельно, по природе передаваемого: есть ли адресат, нужно ли хранить, меняется ли после передачи, сколько потребителей, известен ли порядок. При сомнении предпочтителен канал с наиболее локальным классом отказов.
— Эта глава задаёт словарь всей части IV: последующие главы разбирают преимущественно канал сообщений, но их проектные решения осмысленны только на фоне сопоставления со смежными каналами — общей памятью (см. главу 11 и часть VII) и артефактами (см. главы 37 и 55).
Глава 24. Синхронная и асинхронная коммуникация
Предыдущая глава различила три канала коммуникации в рое — сообщения, общую память и артефакты (см. главу 23) — по тому, что передаётся и где это лежит. Эта глава вводит ортогональное измерение: когда и с каким ожиданием. Один и тот же канал сообщений можно использовать двумя принципиально разными способами. В первом отправитель посылает запрос и останавливает собственную работу до прихода ответа — его поток управления заблокирован чужим. Во втором отправитель выпускает сообщение или событие и продолжает работать, не дожидаясь, пока кто-то на него отреагирует, — связь между моментом отправки и моментом обработки разорвана.
Это различие — синхронное против асинхронного взаимодействия — переносится в мультиагентные системы из распределённых систем без потери смысла, но с поправкой на природу узлов. У агентов единица работы дорогая (вызов модели стоит токенов и секунд), недетерминированная (один и тот же запрос даёт разные ответы) и долгая на фоне сетевых задержек (вызов модели — это секунды и десятки секунд, а не миллисекунды). Поэтому цена блокирующего ожидания у роя агентов на порядки выше, чем у сервиса, отвечающего за миллисекунды, а выгода развязки во времени — соответственно больше. Но асинхронность не бесплатна: она перекладывает сложность с ожидания на согласование состояния, и эта сложность ложится в зоны, где недетерминированный исполнитель особенно уязвим.
Глава рассматривает оба режима как инженерный компромисс между связанностью, латентностью, стоимостью и сложностью отказов, а не как вопрос вкуса или моды. Сначала вводятся определения через связанность во времени, затем разбираются издержки каждого режима, затем — failure modes, которые у каждого режима свои, и в конце — критерии выбора и гибридные схемы, которыми пользуются реальные системы.
Синхронность и асинхронность чаще всего объясняют через блокировку потока, но это следствие, а не суть. Суть — в связанности во времени (temporal coupling): обязаны ли отправитель и получатель присутствовать и быть готовыми одновременно.
Синхронное взаимодействие — отправитель после отправки запроса не продолжает полезную работу до получения ответа. Его прогресс связан с прогрессом получателя: оба должны существовать в перекрывающемся окне времени, получатель должен быть готов принять запрос сейчас, отправитель удерживает контекст и ресурсы всё время ожидания. Классический вызов «оркестратор поручает воркеру подзадачу и ждёт результат, прежде чем планировать следующий шаг» — синхронный, даже если под капотом транспорт построен на очереди.
Асинхронное взаимодействие — отправитель после отправки сообщения продолжает работу, не дожидаясь обработки. Момент отправки и момент обработки развязаны: получателя может не быть в этот миг, он обработает сообщение позже, а отправитель к тому времени уже займётся другим или завершится. Результат, если он нужен, придёт отдельным сообщением, через общую память или артефакт.
Ключ к различению — не в том, есть ли в системе очередь или сокет, а в том, блокирует ли ожидание прогресс отправителя. Очередь сообщений сама по себе не делает взаимодействие асинхронным: если отправитель положил запрос в очередь и тут же сел ждать ответ из другой очереди, ничего не делая, — это синхронный паттерн поверх асинхронного транспорта (request-reply). И наоборот: вызов, оформленный как обычный синхронный API, можно сделать асинхронным на уровне роя, если отправитель не ждёт его завершения, а регистрирует обработчик результата и идёт дальше. Поэтому корректнее говорить о синхронном и асинхронном паттерне взаимодействия, а транспорт — это отдельный слой, который может поддерживать любой из двух.
В сервисной архитектуре синхронный вызов длится миллисекунды, и блокировка потока на это время почти ничего не стоит — поток дешёвый, ожидание короткое. У роя агентов всё иначе по трём причинам.
Во-первых, единица работы долгая. Вызов модели — это, ориентировочно, от секунд до десятков секунд, а цепочка из нескольких шагов рассуждения — минуты. Синхронное ожидание такого ответа замораживает отправителя на минуты, а не миллисекунды.
Во-вторых, отправитель — не дешёвый поток, а агент, удерживающий контекст. Пока оркестратор синхронно ждёт воркера, он держит в своём контексте всё состояние задачи; этот контекст занимает место в окне модели и, если оркестратор сам реализован как длящийся вызов, продолжает тарифицироваться или как минимум блокирует слот.