18+
реклама
18+
Бургер менюБургер меню

Ранас Мукминов – Оркестрация ИИ-агентов. Claude Opus 4.7 (страница 80)

18

Шов незавершённой работы. Задача, запущенная под версией роли N, может ещё выполняться, когда роль уже обновили до N+1. Если работа долгая или была приостановлена и переподхвачена (см. часть X, восстановление и lease), её середина оказывается на стыке версий. Поведение роли посреди задачи не должно «прыгать».

Шов памяти. Самый коварный. Артефакты, записанные старой версией роли в общую память (см. часть VII), переживают её и читаются новыми версиями — иногда спустя дни. Handoff (см. главу 18 и часть VII), сохранённый версией N, разбирается версией N+2. Здесь совместимость нужна не между двумя живыми компонентами, а между прошлым и настоящим — между записью и её будущим читателем.

Сведём ожидания совместимости по швам. Строки — кто производит, столбцы — кто потребляет.

Производитель → Потребитель | Воркер той же версии | Воркер другой версии | Оркестратор | Будущий читатель памяти

Воркер новой версии | по построению | нужна обратная совместимость выхода | нужна прямая совместимость оркестратора | нужна устойчивость схемы артефакта

Воркер старой версии | по построению | нужна прямая совместимость потребителя | нужна обратная совместимость оркестратора | нужна версионная разметка артефакта

Оркестратор любой версии | команда должна пониматься воркером | то же при смешанном парке | — | управляющие записи тоже версионируются

Из матрицы видно, что ни один шов не требует «совместимости вообще» — каждый требует конкретного её вида. Это и есть рабочая дисциплина: для каждого изменения роли ответить, какие клетки матрицы оно затрагивает, и обеспечить именно тот вид совместимости, который в этих клетках нужен.

Перечисленные швы закрываются конечным набором механизмов. Они прямо заимствованы из эволюции схем и версионирования API, с поправками на агентов.

Базовый механизм: версия роли присутствует в каждом сообщении и в каждом артефакте, который роль производит. Не «где-то в конфигурации развёртывания», а в самих данных — потому что артефакт переживёт развёртывание. Сообщение между ролями и запись в общей памяти несут версию роли-автора как обязательное поле метаданных.

{

"produced_by": {

"role": "reviewer",

"contract_version": "2",

"impl_version": "2026-05-14",

"model": "model-x-mini@2026-04"

},

"payload": { "verdict": "changes_requested", "notes": [] }

}

Без этой маркировки невозможны ни диагностика регрессий (см. часть XI: к какой версии относится испорченный артефакт), ни корректная обработка чтения из прошлого (см. часть VII), ни безопасный откат. Маркировка дешева на запись и бесценна при разборе инцидента. Отсутствие версии в артефакте — типичный латентный дефект: он ничего не стоит, пока всё работает, и блокирует расследование ровно тогда, когда нужен.

Принцип толерантного читателя (tolerant reader): потребитель извлекает из входа только то, что ему нужно, и молча игнорирует незнакомое. Это даёт прямую совместимость почти даром — новые поля в выходе соседа не ломают старого потребителя. Симметричный принцип на стороне производителя — не удалять и не переименовывать поля контракта без перехода версии, а только добавлять необязательные. Пара «толерантный читатель плюс аддитивные изменения» закрывает большинство совместимых эволюций без всякой оркестрации выкатки.

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

Когда совместимости нет по построению — контракт действительно изменился, — швы закрывают трансляцией. Между производителем версии N и потребителем версии M ставится адаптер, приводящий один контракт к другому. В терминах ролей это часто отдельная маленькая роль-переходник или функция-преобразователь в оркестраторе.

Адаптеры дают развязку версий, но имеют цену, которую нужно называть прямо: каждый адаптер — это код, который надо поддерживать; набор адаптеров растёт квадратично, если соединять «каждую версию с каждой». Практический приём — канонический промежуточный контракт: все версии транслируются в него и из него, превращая квадрат в линию. Канонический контракт сам становится версионируемым артефактом и должен меняться реже всех остальных — иначе он перестаёт быть точкой стабильности.

Важная оговорка про агентов: адаптер чисто и дёшево транслирует только структурную несовместимость — переименование полей, изменение формата, перестановку. Поведенческую несовместимость адаптер починить не может: нельзя написать преобразователь, который превратит вердикты нового, более строгого ревьюера в распределение, ожидавшееся от старого, не переписав суть его суждений. Попытка «адаптировать поведение» вырождается в ещё одну роль-агент со своим контрактом, версией и недетерминизмом — то есть в перенос проблемы, а не её решение. Отсюда правило: адаптеры — инструмент против структурного дрейфа, поведенческий дрейф закрывается только на стороне версии-источника (исправлением реализации) либо принимается явно как новое поведение всей цепочки.

Иногда дешевле не транслировать, а держать несколько версий роли живыми одновременно и маршрутизировать к нужной (см. главу 27, маршрутизация). Оркестратор направляет задачу версии роли, совместимой с остальным контекстом этой задачи. Это решает шов незавершённой работы (задача доживает на своей версии) и позволяет канареечную выкатку (см. ниже), но удваивает эксплуатационную поверхность: две версии надо мониторить, оплачивать и отлаживать. Параллельное существование оправдано на время перехода и вредно как постоянное состояние — незакрытый переход превращается в вечный парк версий, в котором никто не помнит, какая зачем жива.

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

Отсюда два инженерных следствия. Первое: депрекация требует учёта потребителей и артефактов, а не предположения, что их нет. В долгоживущем рое неучтённая старая запись или давно забытый потребитель — норма, а не исключение; решение «эту версию уже никто не использует» без подтверждения данными есть ставка, которая регулярно проигрывает при первом же чтении из прошлого. Второе: артефакты старых версий имеет смысл активно мигрировать, а не ждать их естественного истечения, — фоновым процессом, который переписывает записи в актуальную схему. Это переносит стоимость совместимости с горячего пути чтения (где старый формат всплывает неожиданно и ломает работающую задачу) на холодный фоновый путь, контролируемый и наблюдаемый. Без миграции число форматов в памяти монотонно растёт, и поддержка чтения превращается в нестареющий долг, который нельзя погасить, не рискуя отложенным отказом.

Ошибки версионирования специфичны тем, что почти все они тихие: система не падает, она деградирует или ведёт себя неверно, проходя при этом формальные проверки.

Самый частый отказ. Промпт роли правят много раз мелкими безобидными штрихами, ни одна правка не объявляется сменой версии, и за месяцы контракт уезжает: роль-планировщик начинает выдавать шаги в другом формате, роль-извлекатель — другой набор полей. Потребители, настроенные на исходный контракт, ломаются не сразу, а по мере накопления дрейфа, и связать поломку с конкретной правкой уже нельзя — версия не менялась, в логах нечего сопоставлять. Защита — режим «промпт есть интерфейс»: любая правка промпта инкрементирует версию реализации и проходит поведенческую проверку.

Носитель сменили (новая модель, новая её версия, иные параметры декодирования), промпт и контракт оставили дословно прежними, версию роли не тронули. Схемные проверки зелёные, а распределение поведения сдвинулось: ревьюер пропускает то, что ловил; планировщик стал многословнее и дороже; критик изменил строгость. Соседи деградируют, причина не локализуется, потому что «ничего не меняли». Защита — обязательная привязка идентификатора носителя к версии роли и поведенческие тесты при любой его смене.

Оркестратор обновили, воркеры — нет (или наоборот). Новый оркестратор шлёт команды в формате, которого старый воркер не понимает, либо ждёт от воркера полей, которых тот не отдаёт; в зеркальной ситуации старый оркестратор не умеет принять расширенный ответ нового воркера. Часто проявляется как загадочные единичные сбои на время окна выкатки. Защита — проектировать обе совместимости явно и выкатывать в дисциплинированном порядке (обычно сначала потребители учатся понимать новое, потом производители начинают его слать).