Суть: три разных вопроса, которые обычно смешивают
Разделение полезно тем, что показывает, где на самом деле лежит сложность.
| Вопрос | Кто отвечает | Что меняется при замене |
|---|---|---|
| Чем агент думает | модель | качество решений |
| Как агента построить | фреймворк, SDK | форма кода приложения |
| Как агент работает | harness | поведение в проде: возобновляемость, границы, подтверждения, наблюдаемость |
Практический смысл различия в том, что третья строка — не про библиотеку, а про рантайм, и заменить её незаметно нельзя: от неё зависит, что происходит при обрыве, при опасном действии и при перезапуске.
Отсюда и тезис, ради которого понятие вообще выделили: переиспользуемый актив — обвязка, а не модель. Модель меняется каждые несколько месяцев и подключается за строчку конфигурации; обвязка накапливает решения про границы, подтверждения и восстановление, и переписывать её при каждой смене модели никто не станет. Тезис принадлежит OpenAI и высказан о собственном продукте — но проверяется он не верой, а тем, что именно они открыли исходниками, а что оставили себе.
Что оказалось переиспользуемым, а что нет
OpenAI открыла три вещи: CLI, app-server и SDK; доступ к модели и управляемые сервисы остались закрытыми. Формулировка прямая: открытый слой — это обвязка и поверхность интеграции.
Разрез сам по себе содержателен. Открыто всё, что описывает, как агент исполняется, и закрыто то, что его питает. То есть заявление «ценность в обвязке» подкреплено действием, которое стоило бы дорого, будь оно неправдой.
Три поверхности отвечают трём способам встроить агента:
- app-server — протокол для клиента: своё приложение говорит с обвязкой по документированному интерфейсу;
- CLI — неинтерактивные ограниченные сценарии, то есть агент внутри скрипта или пайплайна;
- SDK — программный запуск, возобновление и стриминг задач.
Точки расширения — те же три места, где приложение обязано остаться хозяином: свои MCP-сервисы для контекста и инструментов, свои границы песочницы, свой поток подтверждений.
Зачем это нужно, если у тебя уже есть агент. Смысл не в том, чтобы пользоваться чужим ассистентом, а в обратном: занести агента в тот интерфейс, где работа действительно делается, — в панель эксплуатации, в консоль поддержки, во внутреннее приложение одной команды, — вместо того чтобы уводить людей в универсального помощника.
Три примитива, на которых стоит возобновляемость
- Thread — разговор пользователя с агентом; содержит ходы.
- Turn — один запрос пользователя и вся работа агента следом; содержит элементы и стримит инкрементальные обновления.
- Item — единица ввода или вывода: сообщение пользователя, сообщение агента, запуск команды, правка файла, вызов инструмента.
Ценность разбиения не в терминах, а в том, что единицей возобновления становится ход, а не сессия. Оборвалось на середине — восстанавливать надо turn, и он самодостаточен: у него есть начало, конец и перечислимый состав. Ср. Durable Execution, где та же задача решается со стороны графа, а не со стороны протокола.
У каждого элемента явный жизненный цикл из трёх стадий: item/started → необязательные item/*/delta → item/completed с финальной нагрузкой. Это не украшение протокола, а то, что позволяет интерфейсу начать рисовать сразу, дописывать по мере поступления и зафиксировать результат в конце, — вместо ожидания готового ответа.
Поток — долговечный контейнер: его создают, возобновляют, ветвят и архивируют, а история сохраняется, чтобы клиент после переподключения собрал ту же ленту. Ветвление здесь не деталь реализации: оно означает, что от одного состояния разговора можно уйти двумя путями, не потеряв исходный.
Разговор открывается рукопожатием initialize — единственным запросом, который клиент обязан послать до всех остальных. Смысл не в приветствии: на этом шаге стороны договариваются о версии протокола, флагах возможностей и умолчаниях. Именно это делает протокол расширяемым без поломки существующих клиентов, что было прямым требованием, когда обвязку начали встраивать в чужие продукты.
Подтверждение как примитив протокола, а не решение в коде
Самое переносимое здесь. Обычно «спросить человека перед опасным действием» — это условие в коде приложения, и потому оно расползается, забывается на новом пути и по-разному ведёт себя в разных местах. В протоколе это отдельные типы запроса, инициируемые сервером:
| Запрос | На что спрашивают |
|---|---|
item/commandExecution/requestApproval |
выполнение команды оболочки |
item/fileChange/requestApproval |
правку файлов |
item/permissions/requestApproval |
выдачу доступа песочнице |
tool/requestUserInput |
структурированный ввод от человека |
mcpServer/elicitation/request |
запрос данных со стороны MCP-сервера |
Клиент отвечает одним из трёх: accept, decline, cancel.
Два наблюдения, которые стоит унести независимо от продукта:
Типов запроса пять, а не один. «Подтверждение» — не однородная вещь: разрешить команду, разрешить правку файла и расширить права песочницы это разные решения с разной ценой ошибки, и сваливать их в один диалог «Разрешить действие? Да/Нет» значит терять различие ровно там, где оно нужно (Rollback Boundary, класс обратимости).
cancel отделён от decline. Отказать в конкретном действии и прервать всю работу — разные исходы, и путать их дорого: агент, получивший «нет» на один шаг, должен уметь пойти другим путём, а получивший «отмена» — остановиться. Интерфейс, где есть только «Да/Нет», этой разницы выразить не может.
Пункт чеклиста tools/approval-path спрашивает, определено ли, какие действия не выполняются без подтверждения человека. Здесь видно, как выглядит ответ, когда он дан конструкцией: не политикой в коде, а формой протокола, мимо которой не пройти.
Протокол — это перевод, а не внутренности наружу
App Server — одновременно протокол между клиентом и сервером и долгоживущий процесс, внутри которого работают ядра. Четыре части: читатель stdio, обработчик сообщений, менеджер потоков и сами ядра; менеджер поднимает по одному ядру на поток.
Существенно устройство середины: читатель и обработчик работают слоем перевода. Они превращают запросы клиента во внутренние операции ядра, слушают его внутренний поток событий и сворачивают низкоуровневые события в небольшой набор стабильных, пригодных для интерфейса уведомлений.
Это ответ на вопрос, который возникает у всякого, кто отдаёт наружу свой рантайм. Отдать внутренние события как есть — значит зафиксировать внутреннее устройство в публичном контракте и потерять свободу его менять. Слой перевода разводит две скорости: внутри можно перестраивать что угодно, снаружи набор уведомлений остаётся маленьким и стабильным. Тот же приём, что Orchestrator Adapter, но обращённый в другую сторону — не приложение защищается от фреймворка, а рантайм защищает свой контракт от собственных внутренностей.
Транспорт — JSON-RPC поверх stdio, построчным JSON. Оговорка авторов: это не строгий JSON-RPC 2.0 — заголовок "jsonrpc": "2.0" опущен, кадрирование построчное. Схему клиентских привязок генерируют командой codex app-server generate-json-schema, то есть привязку на своём языке не пишут руками.
Клиент не может быть источником истины
Три способа встроить обвязку — локальное приложение или IDE, веб и терминальный интерфейс — различаются не удобством, а тем, где живёт состояние.
Локальный клиент несёт с собой бинарник обвязки, прибитый к проверенной версии, и держит с ним двусторонний канал. Веб-версия запускает обвязку в контейнере на рабочем месте с выгруженным репозиторием, а браузер разговаривает с бэкендом по HTTP и SSE.
Причина этой раскладки названа прямо: веб-сессии эфемерны — вкладку закрывают, сеть отваливается, — поэтому веб-приложение не может быть источником истины для долгой задачи. Состояние и прогресс держат на сервере, и тогда работа продолжается независимо от того, смотрит ли на неё кто-нибудь.
Терминальный интерфейс исторически был исключением — он жил в одном процессе с циклом агента и говорил с ядром напрямую. Его переводят на общий протокол, и выигрыш описан так: агент сможет работать на удалённой машине рядом с вычислениями, продолжая работу, пока ноутбук спит, а управление и живые обновления останутся локальными.
Как выбирать поверхность
Пять вариантов, и у каждого назван свой размен:
| Вариант | Когда | Чем платишь |
|---|---|---|
| Обвязка как MCP-сервер | уже есть MCP-контур, нужен агент как вызываемый инструмент | получаешь только то, что выражается через MCP; богатая семантика сессии (например, обновления диффов) через него не проходит |
| Кросс-провайдерный протокол обвязки | нужна одна абстракция над несколькими провайдерами | такие протоколы сходятся к общему подмножеству возможностей, и специфичное выразить труднее |
| App Server | нужен весь цикл агента стабильным потоком событий | интеграционная работа: клиентскую привязку пишешь сам |
| Однократный запуск командой | автоматизация и пайплайны | неинтерактивно: один прогон, структурный вывод, явный успех или отказ |
| SDK | нативная библиотека внутри своего приложения | меньше языков и меньшая поверхность, чем у протокола |
Строка про кросс-провайдерный протокол — самая полезная для базы. Она называет цену переносимости в общем виде: абстракция над несколькими поставщиками сходится к их пересечению, и то, что есть только у одного, через неё не проходит. Это же соображение стоит за границей адаптера в Orchestrator Adapter — с той разницей, что там абстракцию пишешь сам и сам решаешь, что в неё пускать.
Права песочницы как объявленное значение
Политика доступа выражается перечислимым значением, а не набором условий:
readOnly · workspaceWrite · dangerFullAccess · externalSandbox
и уточняется полями writableRoots, readableRoots, networkAccess, includePlatformDefaults.
Agent Sandboxing описывает чем изолировать — Docker, gVisor, микровиртуальные машины. Здесь другое измерение: как права объявляются. Перечислимое значение можно прочитать в конфиге, залогировать, сравнить между запусками и запретить на уровне политики; рассыпанные по коду проверки — нельзя. externalSandbox в этом списке отдельно ценен: он честно говорит «изоляцию обеспечивает вызывающая сторона», вместо того чтобы делать вид, что её обеспечивает обвязка.
Поток событий — это и есть трассировка
События идут пятью группами: жизненный цикл хода (turn/started, turn/completed, turn/diff/updated, turn/plan/updated), жизненный цикл элемента (item/started, item/completed), дельты (item/agentMessage/delta, item/plan/delta, item/reasoning/summaryTextDelta, item/commandExecution/outputDelta), события потока (thread/started, thread/archived, thread/closed, thread/status/changed) и предупреждения (configWarning, warning).
Существенно то, что этот поток не добавлен ради наблюдаемости — он и есть способ, которым клиент узнаёт о происходящем. Значит трассировка не может «отвалиться» или быть выключенной для экономии: без неё приложение просто не работает. Ср. Agent Observability, где трассировка обычно пристраивается сбоку и потому первой отключается под нагрузкой.
turn/plan/updated стоит отдельного упоминания: план агента — часть протокола, а не текст внутри сообщения. Это ровно тот минимум видимости, который Agent Execution Platform называет обязательным для эксплуатации.
Агент безголовый: поверхность решает, а не он
Разница между поверхностями исчерпывается тем, что добавляет обёртка, а не тем, что меняется в агенте:
| Терминал | Веб | |
|---|---|---|
| вывод | текст | элементы интерфейса |
| вызовы инструментов | строки в поток ошибок | отдельные компоненты результата |
| подтверждение | запрос в потоке ввода | кнопки |
| время жизни | до завершения процесса | сессия переживает вкладку |
| ввод | один раз при запуске | непрерывно |
Граница проходит там, где обычно её размывают: агент не знает про хранилище. Он получает историю сообщений или не получает и работает дальше; решение загружать и сохранять принимает поверхность. Как только агент начинает сам ходить в базу, поверхности перестают быть взаимозаменяемыми, и вторую уже не написать, не тронув первую.
Это же объясняет, почему состояние долгой задачи не может жить в клиенте: разобрано выше, в разделе про эфемерность веб-сессии.
Расширение событиями, а не наследованием
Точки расширения выше названы списком мест. Общая форма у них одна: обвязка объявляет события жизненного цикла, расширение подписывается. Подписчик может пропустить вызов, заблокировать его или изменить данные перед тем, как обвязка продолжит; несколько подписчиков выстраиваются в цепочку.
Набор событий держат маленьким намеренно. Пяти хватает на практике: старт сессии, вызов инструмента, результат инструмента, момент перед уплотнением контекста, завершение сессии. Добавить шестое потом несложно; начинать с пяти — то, что оставляет контракт читаемым.
Почему именно события, а не наследование или конфигурация:
- сквозные заботы не принадлежат ни одному инструменту — журналирование каждого вызова, запрет писать в чувствительный файл, обёртка команды в дополнительную изоляцию, автосохранение при выключении. Внутри инструмента им не место, а снаружи нужна точка подключения;
- блокировка возвращает модели причину текстом, а не молча отменяет вызов: модель видит формулировку политики и передаёт её пользователю;
- изменение события позволяет ужесточать, не переписывая — например, дописать команде обёртку изоляции.
Широкий хук на высокочастотном инструменте — типовая ошибка этого слоя. Подписчик, который перехватывает вызов часто используемого инструмента и принудительно перенаправляет его на другой, чинит сценарий, ради которого написан, и ломает поведение в множестве несвязанных. Ошибка воспроизводимая: при автоматической оптимизации обвязки ровно такой хук сочиняли обе сравниваемые конфигурации, и разошлись они не в том, придумали или нет, а в том, было ли чем его отвергнуть (Harness Optimization). Практический вывод для событийного слоя: чем чаще вызывается инструмент, тем уже должна быть область действия подписчика на него.
Соотношение с подтверждениями: событийный слой и слой режима решают разные задачи, и это разобрано в Human in the Loop — режим отвечает «кто решает», события «какие правила действуют независимо от ответа».
Мягкий контур и жёсткий: чем принуждается прохождение шагов
Всё описанное выше — мягкий контур: обвязка объявляет умения, субагентов и порядок работы, а применит ли их модель на этой задаче, решает модель. Промпт может сработать, а может не сработать, умение может подгрузиться, а может остаться непрочитанным, и управлять этим нечем — это и есть свойство слоя, а не дефект конкретной реализации. Для большинства задач приемлемо: цена отклонения — переделка.
Там, где цена отклонения выше переделки — регулируемая среда, обязательные требования по безопасности, прослеживаемость, — поверх ставят жёсткий контур: отдельный слой снаружи цикла, который не советует, а проверяет. Он ведёт задачу по объявленным чекпоинтам, ловит отклонение на том шаге, где оно возникло, и сам отправляет на переделку, не спрашивая человека. Разница формулируется одной строкой: в мягком контуре последовательность шагов — намерение, в жёстком — принуждаемый инвариант.
Два механизма, которыми это делается, стоит назвать отдельно, потому что они разные:
- Паспорт задачи — объект, который едет вместе с задачей через все этапы и накапливает результаты каждой проверки. Метрики качества собираются не в конце и не сбоку, а по ходу, и к финальному гейту приходят вместе с артефактом. Приём взят с конвейерного производства, где такой паспорт сопровождает изделие; смысл тот же — на выходе видно не «прошло / не прошло», а где именно и чем подтверждено.
- Политики как код — требования, которые иначе проверялись бы чеклистом в конце, раскладываются в файлы инструкций и умения и применяются в момент работы. Тогда на гейт приходит уже соответствующий продукт, а не тот, который сейчас развернут обратно. Выигрыш здесь не в скорости проверки, а в снятии возвратов: разработчик перестаёт узнавать о требовании на сдаче.
Чем за это платят. Жёсткий контур — вторая система рядом с первой: её надо описывать, поддерживать и держать согласованной с меняющимся процессом. Утверждение «на выходе всегда полное соответствие» звучит от тех, кто её построил, и проверить его снаружи нечем, а стоимость владения в таких рассказах обычно не называется. Практическая граница: жёсткий контур оправдан там, где отклонение обнаруживается поздно и стоит дороже, чем содержание контура — иначе это перенос неопределённости из модели в конфигурацию, где её столько же, но выглядит она надёжнее.
Соотношение с соседями: событийный слой выше даёт точки, в которых можно заблокировать или изменить вызов, — жёсткий контур это те же точки, собранные в маршрут с состоянием. Кто вправе разрешить действие — вопрос отдельный и разобран в Agent Control Plane; здесь речь только о том, гарантировано ли прохождение объявленных шагов.
У обвязки есть измеримое качество, и оно настраивается
Обвязка обсуждается как устройство — из каких частей состоит и как они соединены. Отдельно от этого стоит вопрос, которого устройство не закрывает: насколько она хороша и как это узнать.
Ответ измеряется тем же, чем качество агента: доля решённых задач на наборе сценариев. Существенно то, что обвязка при этом ведёт себя как настраиваемый параметр — промпты, описания и реализации инструментов, хуки и логика цикла образуют пространство поиска, по которому можно двигаться по трассам провалов. Разбор дисциплины — Harness Optimization; там же измерение, показывающее, что правка текста промпта чинит не хуже правки кода, но ломает вдвое чаще.
Чем это отличается от Agent Execution Platform
Заметки соседние, и границу надо держать явно:
- Agent Execution Platform отвечает, из каких слоёв состоит рантайм, если строишь его сам: вход, идентичность, плоскость управления, оркестрация, модель, память, инструменты, телеметрия.
- Эта заметка отвечает, что из этого берут готовым и по какому протоколу к нему подключаются.
Различие практическое: первая — про проектирование, вторая — про решение «строить или встроить», и про то, какой поверхностью потом придётся расплачиваться (Orchestrator Adapter: обвязка — вторая точка привязки помимо фреймворка оркестрации).
Связано с
- Sandbox Abstraction — контракт со средой исполнения, одна из точек расширения обвязки
- Agent First Repository — среда вокруг обвязки: как устроен репозиторий, код которого пишет агент
- Agent Execution Platform — из чего рантайм состоит; здесь — что из этого берут готовым
- Agent Architecture — сборка цикла своими руками, то есть противоположный выбор
- Orchestrator Adapter — обвязка как ещё одна поверхность привязки
- Human in the Loop — подтверждение человеком; здесь показана его протокольная форма
- Agent Sandboxing — чем изолировать; здесь — как объявлять права
- Agent Observability — трассировка, которая тут не пристроена сбоку, а несущая
- Harness Optimization — как обвязку улучшать и чем мерить: устройство отвечает «как построена», это — «насколько хороша»
- Durable Execution — возобновляемость со стороны графа, а не протокола
- MCP — способ, которым приложение отдаёт обвязке свои инструменты и контекст
- Agent Control Plane — кто вправе разрешить действие; здесь — принуждается ли объявленный маршрут
- Parallel Agent Dispatch — та же обвязка со стороны человека, который ведёт несколько задач сразу