Харнесс-инжиниринг для ИИ-агентов: проектирование контуров управления
Автоматический перевод Эта статья была автоматически переведена с оригинальной английской версии.
Цикл ризонинга агента выбирает следующее действие. Его харнесс предоставляет контекст, валидирует предлагаемые tool calls, авторизует их, отправляет разрешённые вызовы на выполнение, записывает результаты и решает, завершена ли задача.
Первые три из этих задач уже рассмотрены в статье. Память предоставляет контекст, tool use определяет, что можно предложить, а безопасность решает, что будет выполнено. Они вышли отдельными публикациями, потому что это разные инженерные задачи. Выбор между Qdrant и pgvector никак не связан с написанием правила deny для PreToolUse.
При этом у них есть общая точка: промежуток между тем, как модель называет действие, и тем, как машина его выполняет. Каждый из этих компонентов отвечает на вопрос об этом промежутке, а харнесс — это код, который удерживает его открытым достаточно долго, чтобы задать все три вопроса.
Для инженеров, которые создают или ревьюят харнессы coding-агентов, остаётся решить, действительно ли завершённая работа закончена, и доказать, что каждый контроль в цикле оправдывает свои затраты. Часть 5 была посвящена рантайму, который поддерживает процесс под ним.
Помимо первой явной проверки приёмки, каждый добавленный ретрай, handoff или эвалуатор — это гипотеза о наблюдаемом сбое. Он заслуживает места только тогда, когда контролируемое сравнение показывает, что компонент помогает.
Самую простую проверку приёмки легко написать для небольшого исследовательского агента, которого мы создавали в этой серии, — агента LangGraph, который получает рыночные данные и пишет аналитический отчёт. Хук вне модели валидирует отчёт по схеме и проверяет, что в нём действительно есть тикеры акций; некорректный отчёт не позволяет завершить запуск. Двенадцать строк обычного кода — и модель не может сама объявить свой вывод корректным. Собственный gate репозитория мягче: эвалуатор с новым контекстом голосует, а затем решение принимает человек. (Часть 4 набрасывает детерминированную версию.)
Этот пример не показывает самое интересное: что происходит, когда свидетельства неоднозначны, когда ретрай может списать деньги с пользователя дважды или когда работа переживает сессию, в которой началась. Для этого нужна задача с более чёткой границей pass/fail, чем исследовательский отчёт. Исследовательский агент остаётся примером для проверки приёмки; для случаев с ретраями и handoff к нему добавляется небольшой вымышленный репозиторий магазина. Задача по коду — снизить порог для автоматической скидки 10% со значения $100 до $75 в src/checkout.py. В репозитории есть две обязательные проверки:
pytest tests/test_checkout.pyпроверяет расчёт скидки.pnpm playwright test tests/checkout_discount.spec.tsдобавляет товар за $80 в локальный тестовый магазин и проверяет, что на странице checkout отображается скидка $8.
Пример — учебный fixture, а не реальное приложение или бенчмарк. Каждый запуск начинается с одного и того же коммита и одних и тех же seeded test data. Харнесс может принять изменение только в том случае, если обе команды завершаются успешно, а трейс связывает эти результаты с протестированным коммитом.
Диаграмма показывает путь изменения скидки от предложения до свидетельства. Харнесс передаёт задачу и файлы, проверяет аргументы и разрешения предлагаемого вызова edit_file и отправляет разрешённый вызов на выполнение. После того как рантайм применяет изменение, харнесс запускает названные unit-тесты и acceptance-тесты в браузере. Невыполненная команда возвращается модели как свидетельство для следующего хода; две успешные команды делают изменение кандидатом на приёмку.
Чем владеет харнесс
В разборе цикла Codex от OpenAI описан базовый цикл. Харнесс собирает промпт, запрашивает у модели следующее действие, отправляет разрешённый tool call в рантайм и добавляет результат. Затем снова задаёт вопрос. Это повторяется, пока харнесс не примет результат или не вернёт управление пользователю.
В конкретной реализации несколько обязанностей могут быть объединены в один процесс. Но границы отказов всё равно различаются:
| Термин | Задача | Пример для coding-агента |
|---|---|---|
| Модель | Предлагает текст, tool call или финальный ответ | Предлагает изменить src/checkout.py |
| Цикл ризонинга | Выбирает следующий шаг из доступного контекста | Изучить, изменить, протестировать, снова изучить |
| Харнесс | Предоставляет контекст, валидирует предложения, авторизует их, отправляет разрешённые вызовы, записывает результаты и проверяет завершение | Разрешает изменения в рамках src/ и требует оба названных теста |
| Рантайм | Выполняет разрешённые вызовы и сохраняет состояние за пределами worker-процесса | Лог сессии, сэндбокс, хранилище чекпоинтов, бэкенд трейсов |
Строка про рантайм охватывает четыре компонента: сессию, сэндбокс, чекпоинт и трейс. Все четыре либо хранят состояние, либо ограничивают выполнение. Модель предлагает действие, а цикл ризонинга выбирает следующий шаг. Харнесс решает, может ли предложенный вызов быть выполнен и достаточно ли свидетельств для завершения, поэтому ему посвящена отдельная статья. Часть 5 рассматривает харнесс вместе с этими четырьмя компонентами как один из пяти примитивов, которые нужно разместить до выхода в прод; здесь он снова вынесен отдельно.
Когда возникает сбой, диагностируйте границу, которая должна на него реагировать. Плохому плану могут понадобиться более точные инструкции или улучшенный ризонинг модели. Если edit_file обращается к пути за пределами src/, харнесс должен отклонить вызов. Если процесс сэндбокса завершается до применения изменения, это проблема рантайма: он должен перезапустить worker или сообщить о падении.
Где применяются предыдущие части
Строка с харнессом выше выполняет большую часть работы в таблице, и именно к ней относятся части 2, 3 и 4. Каждая из них решает один вопрос для отдельного хода:
| Предыдущая часть | Что она решает для этого хода | Где действует в walkthrough следующего раздела |
|---|---|---|
| Часть 2 — память | Какое предыдущее состояние попадает в промпт | Шаг 1, сборщик контекста |
| Часть 3 — tool use | Какие действия существуют и как выглядит валидированный результат | Валидация аргументов на шаге 3 и форма результата на шаге 4 |
| Часть 4 — безопасность | Может ли именно этот вызов быть выполнен сейчас | Шаг 3, проверка пути и решение об одобрении |
| Часть 6 — эта статья | Завершает ли полученное свидетельство запуск | Шаги 5–7, проверки приёмки и трейс |
Части 3 и 4 используют один и тот же шаг 3, и именно это подтверждает идею рассматривать их как одну программу. Один и тот же слой кода харнесса, который отклоняет некорректный аргумент, также отклоняет вызов, который разрешён, но ещё не одобрен. Если разделить эти проверки между двумя сервисами, они начнут расходиться, и вызов, прошедший валидацию схемы в одном месте, будет авторизован в другом, где эту схему никто не видел.
Для отладки разделение всё же важно: изменение не того файла — это правило пути из части 4, а не проблема retrieval из части 2. В разделе ближе к концу статьи это превращено в таблицу маршрутизации.
В собственном кейсе про harness-engineering OpenAI описывает загружаемый экземпляр приложения для каждого worktree. Команда также встроила browser automation в окружение агента и открыла ему логи, метрики и трейсы.
Задача вроде «ни один спан в этих четырёх критических пользовательских сценариях не должен превышать две секунды» стала тестируемой, потому что агент мог запускать приложение и запрашивать те же сигналы, которые проверял бы инженер. Кейс специфичен для продукта. Переносимым является условие, лежащее в основе результата: приложение и его сигналы производительности должны быть доступны внутри окружения агента.
Lopopolo, автор этого кейса, ведёт полевое руководство по harness-engineering. В нём названы два рычага, которые используются в этой статье: зафиксировать модель и coding-агента как чёрный ящик, а контекст и инструменты проектировать вокруг них. Его формулировка также объясняет, почему значительная часть харнесса в итоге оказывается обычным кодом.
Планка качества организации, процедуры, история исключений и отношения полномочий находятся за пределами того, что может знать универсальная модель. Харнесс представляет их как инструкции репозитория, правила разрешений и проверки приёмки. Каждый принятый запуск может возвращать свои уроки в эти артефакты, вместо того чтобы следующая сессия заново их открывала.
Проследим изменение скидки от предложения до приёмки
Для описанной выше задачи со скидкой модель предлагает изменить calculate_discount в src/checkout.py. До того как это изменение будет засчитано как прогресс, происходит несколько вещей:
- Сборщик контекста передаёт задачу, инструкции репозитория, релевантные файлы, предыдущие результаты инструментов и текущий план.
- Модель предлагает вызов
edit_fileс путём и текстом замены. - Граница инструмента — код харнесса между предложением и выполнением — валидирует аргументы, проверяет путь относительно разрешённой области и запрашивает одобрение, если операция этого требует.
- Рантайм применяет изменение в сэндбоксе и возвращает структурированный результат.
- Харнесс запускает
pytest tests/test_checkout.py, затемpnpm playwright test tests/checkout_discount.spec.tsи считывает оба exit code. Браузерный тест проверяет видимую скидку $8 в seeded-корзине на $80. - Харнесс решает, что означают результаты. Невыполненная проверка становится новым контекстом для следующего хода модели, а успешный запуск делает задачу кандидатом на завершение.
- Успешный результат становится свидетельством завершения только после того, как харнесс записывает в трейс команду, exit code и версию протестированного артефакта.
После шага 2 ни один файл ещё не изменён. Харнесс может отклонить ../../secrets.env, потребовать одобрение деструктивной команды или остановить запуск, исчерпавший бюджет. Это последний дешёвый момент. После выполнения тестов харнесс сам считывает их exit code. Модель не может сама объявить своё изменение успешно прошедшим проверку.
В трейсе должны быть видны предлагаемые путь и текст замены, решение о разрешении, изменившиеся файлы, протестированный коммит и результаты обеих команд. Одно финальное сообщение done без этих записей не доказывает, что изменение прошло обязательные проверки.
Решаем, где применять каждое правило
Требование о том, что tests/checkout_discount.spec.ts должен завершаться успешно, относится к детерминированному коду, а не к промпту. Харнесс отправляет команду Playwright в рантайм, считывает её exit code и не даёт завершить запуск, пока тест не проходит. Промпт может напомнить модели запустить тест. Но он не может помешать модели объявить успех без свидетельств.
Другие правила подходят для других слоёв:
| Где размещать правило | Что подходит | Пример |
|---|---|---|
| Промпт или skill | Порядок поиска, соглашения по коду и формат плана | Прочитать AGENTS.md перед изменением checkout-кода |
| Граница инструмента | Валидация аргументов, разрешённые пути, одобрения и доступ к инструментам | Разрешать запись только в src/ |
| Детерминированный код | Бюджеты, тайм-ауты, ретраи, exit code тестов и release gate | Не закрывать запуск, пока тест Playwright не проходит |
| Эвалуатор с новым контекстом | Визуальное ревью или критерии, требующие суждения, похожего на человеческое | Сравнивать сгенерированную диаграмму с письменной рубрикой |
Контракты инструментов разделяют предложение и разрешение
Для задачи со скидкой нужны только изменения файлов и команды тестов. У API, меняющего состояние, другой тип отказов, поэтому в этом разделе переключимся на другой пример. Допустим, при подготовке тестовых данных агент может вызывать create_test_order в staging-сервисе заказов. Этот инструмент не входит в проверки приёмки задачи со скидкой. Он полезен здесь, потому что тайм-аут может скрыть, создал ли сервис заказ.
Границе инструмента недостаточно описания на естественном языке. Ей нужен явный контракт инструмента. Часть 3 обосновывала его со стороны модели: понятные действия, компактная обратная связь, восстанавливаемые ошибки. Харнессу тот же контракт нужен по другой причине. Он должен без обращения к модели решить, можно ли выполнять вызов и можно ли повторить неудачный вызов. Для create_test_order это означает контракт со следующими свойствами:
- валидированные аргументы, чтобы некорректный ввод отклонялся до выполнения
- структурированный результат, например
{ "order_id": "123", "created": true }, чтобы последующим проверкам не приходилось разбирать свободный текст - категория эффекта, фиксирующая, получает ли вызов только информацию или меняет файл, запись в базе данных либо внешний сервис. Она также фиксирует, безопасно ли повторять вызов. Эта метка сообщает харнессу, может ли автоматический ретрай продублировать работу. Харнесс может повторить
get_order_status, если сервис определяет этот lookup как операцию только для чтения. Но нельзя бездумно повторятьcreate_test_order, поскольку первый вызов уже мог создать заказ - политика тайм-аутов и ретраев, чтобы потеря ответа не запускала неограниченную последовательность вызовов
- правило разрешений, указывающее требуемое одобрение. Получение статуса заказа может выполняться автоматически, а создание заказа может требовать подтверждения
Описание на естественном языке — это текст, который показывается модели. Например: «Создай тестовый заказ для проверки checkout». Эта фраза помогает модели понять, когда предлагать create_test_order. Но она не авторизует вызов. В этом примере MCP-клиент харнесса валидирует аргументы, применяет собственные правила и перед отправкой чего-либо проверяет доверие к серверу, требования к одобрению и безопасность ретрая. Это упорядоченная лестница deny/ask/hook/allow из части 4, дополненная одним вопросом: можно ли повторно отправить вызов, который уже завершился ошибкой.
MCP-сервер публикует клиенту описания инструментов и необязательные аннотации поведения. Некорректный или вредоносный сервер может описать инструмент, меняющий состояние, как безопасный. Клиент, автоматически поверивший этому утверждению, может выполнить или повторить create_test_order без одобрения и создать дубликат. Поэтому спецификация MCP требует, чтобы клиенты рассматривали аннотации инструментов как недоверенные, если сам сервер не является доверенным.
Спецификация не задаёт единой универсальной настройки доверия, поэтому для своего деплоя нужно явно определить политику доверия; сервер не может сделать собственные аннотации достоверными. Эта политика решает, какие метаданные могут влиять на решения о разрешениях или ретраях, а какие аннотации остаются лишь справочными.
Ретрай вызова, меняющего состояние, требует защиты от повторного воспроизведения
Часть 5 добавляет idempotency key к каждому tool call с побочным эффектом. Харнесс решает, когда этот ключ должен взять на себя основную защиту. create_test_order создаёт заказ, но HTTP-ответ теряется. Харнесс видит тайм-аут и не может определить, завершил ли сервер запрос. Повтор вызова может создать второй заказ.
Lookup статуса можно повторить, если сервис определяет его как операцию только для чтения. Для вызова создания нужен ключ: клиент прикрепляет уникальный идентификатор запроса, а сервис при повторном обнаружении этого идентификатора возвращает первый результат вместо создания ещё одного заказа. Без такой защиты харнесс должен проверить существование заказа или запросить решение человека, прежде чем делать новую попытку. AWS документирует этот паттерн в руководстве по idempotent API.
Для приёмки нужны независимые свидетельства
Успешный ответ create_test_order доказывает только, что инструмент вернул данные. Он не доказывает, что coding-задача прошла тесты. Если последующий браузерный тест зависит от staged-заказа, харнесс должен валидировать схему ответа и всё равно запустить этот тест до приёмки изменения в коде.
Некоторые критерии нельзя свести к exit code. Для отдельной задачи по визуальному дизайну эвалуатор с новым контекстом может сравнить отрендеренную страницу или диаграмму с письменной рубрикой — «с новым контекстом» означает вторую сессию модели, которая не имеет истории запуска и читает созданные артефакты, а не транскрипт. Прежде чем позволять такому эвалуатору блокировать завершение, сравните его работу с человеческими ревью.
Для миграции payment-адаптера нужен handoff
Снова сменим задачу, но останемся в вымышленном репозитории магазина. Теперь агент должен перенести checkout с payment-адаптера v1 на v2. Работа охватывает checkout-handler, payment-клиент, конфигурацию и тесты, поэтому может не уложиться в одну сессию модели — один непрерывный фрагмент контекста модели, завершившийся перезапуском или намеренным новым стартом, а не перенесённый дальше.
До достижения лимита контекстного окна первая сессия изменяет несколько файлов, запускает локальный payment-сэндбокс и оставляет tests/payment_migration.spec.ts не проходящим. Этот браузерный acceptance-тест проводит один платёж через адаптер v2 и проверяет записанный ID провайдера. Сводка разговора может сориентировать следующую сессию модели, но не может перезапустить сэндбокс или доказать, какие файлы сейчас изменены.
Следующая сессия должна восстановить три вещи:
| Что необходимо восстановить | Что входит | Как это может сломаться |
|---|---|---|
| История разговора | Сообщения, tool calls и возвращённые результаты | Старые детали вытесняют текущую задачу |
| Рабочее окружение | Файлы, payment-сэндбокс и состояние браузерного теста | В транскрипте сказано, что сервис работает, хотя он уже завершился |
| Прогресс задачи | План, завершённые проверки, ожидающее одобрение, следующий шаг | Следующая сессия повторяет уже выполненную работу |
Компрессия заменяет старые сообщения кратким summary, чтобы текущая сессия могла продолжиться. Handoff прогресса фиксирует то, что нужно следующей сессии: текущую ветку, изменённые файлы, последнюю команду теста и её вывод, а также следующий нерешённый шаг.
Файл handoff — это document memory, написанный для одного читателя — следующей сессии модели; это другой артефакт, не тот чекпоинт, который восстанавливает рантайм. Чекпоинт отвечает на вопрос, где остановилось выполнение. Handoff отвечает на вопрос, что означает текущая работа и что осталось. Если восстановить чекпоинт без handoff, следующая сессия получит процесс, который можно возобновить, но не поймёт, какую из четырёх затронутых областей — handler, client, configuration или tests — она уже завершила. Именно это приводит к дублированию работы.
Если старый разговор содержит устаревшие предположения, харнесс может начать новую сессию модели с этим handoff и текущим рабочим пространством. Замена упавшего worker и восстановление его процессов — отдельная задача восстановления рантайма.
Небольшому изменению документации не нужны ни один из этих механизмов. Для миграции payment-адаптера handoff нужен, когда работа переходит между сессиями: следующая сессия модели должна восстановить и рабочее пространство, и статус задачи.
В экспериментах Anthropic с долго работающими coding-агентами между сессиями использовались история git и файл прогресса. В последующем отчёте о дизайне харнесса Anthropic разделяет компрессию и handoff с новым контекстом и сообщает, что handoff добавляет оркестрацию, расход токенов и wall time, но не публикует цифры, позволяющие отнести эти затраты именно к handoff.
Используйте трейсы, чтобы различать три сбоя
Следующие три строки — иллюстративные наброски трейсов, а не измеренные запуски или вывод companion lab. Каждая строка показывает отдельный сбой и, следовательно, отдельную реакцию харнесса.
| Что фиксирует трейс | Что произошло | Правильная реакция |
|---|---|---|
Read-only вызов get_order_status возвращает 503; state-changing вызов не выполняется | Временный lookup завершился ошибкой | Повторить lookup с ограничением числа попыток и backoff |
create_test_order завершается по тайм-ауту, затем lookup статуса находит заказ 123 с idempotency key checkout-42 | Сервис создал заказ, но ответ потерялся | Вернуть существующий заказ; не создавать новый |
Изменение и unit-тест проходят, но в трейсе нет результата tests/checkout_discount.spec.ts для протестированного коммита | Отсутствует обязательное свидетельство приёмки | Не закрывать запуск и отправить браузерный acceptance-тест |
Временный на вид сбой не делает любой вызов безопасным для повтора. В первой строке речь идёт о lookup только для чтения. Во второй — о запросе, меняющем состояние, поэтому idempotency key и серверный статус определяют, разрешена ли ещё одна попытка создания. В третьей строке нет отказа инструмента; харнесс просто ещё не собрал свидетельство, необходимое для приёмки изменения скидки.
Чат-транскрипт фиксирует то, что видела модель. Он не может доказать, зафиксировал ли сервис заказ до исчезновения ответа. Транскрипт — это версия событий со стороны агента; трейс показывает, что фактически сделала машина. Если они расходятся, верьте трейсу. Трейс должен связывать вызов клиента, решение об одобрении, idempotency key, результат сервера или lookup статуса, протестированный коммит и результат acceptance-теста. Эти поля сообщают харнессу, по какому из трёх путей он идёт.
| Повторяющийся симптом | Небольшое изменение для проверки | Что измерять |
|---|---|---|
| Временные сбои lookup только для чтения | Ограниченный ретрай с backoff | Доля восстановлений, дополнительные вызовы, wall time |
| Возобновлённые сессии повторяют завершённую работу | Структурированный handoff прогресса | Дублирующие tool actions после возобновления |
| При завершении отсутствуют обязательные тесты | Fail-closed gate приёмки | Задачи, принятые без всех обязательных проверок |
| После детерминированных проверок остаются визуальные дефекты | Эвалуатор с новым контекстом и рубрикой | Найденные дефекты, ложные отклонения, время ревью |
| Агент изменяет файлы за пределами своей области | Более узкие разрешения инструментов | Заблокированные вызовы и ручные переопределения |
| Вспомненная память вытесняет текущую задачу | Ограничить recalled facts; ранжировать до инъекции | Токены на recall, завершённые задачи, стоимость задачи |
Прежде чем добавлять компонент, назовите повторяющийся сбой, который он должен уменьшить, и число, которое вы будете отслеживать. Удалите компонент, если контролируемое сравнение не сдвигает это число достаточно, чтобы окупить его стоимость. Большинство харнессов, которые я видел, растут иначе: кто-то сталкивается с неудачным запуском, добавляет guard, а затем guard остаётся навсегда, потому что никто не может доказать безопасность его удаления. Так вы получаете цикл, к которому никто не хочет прикасаться.
Превратить эти повторяющиеся сбои в версионируемый regression suite — отдельная задача. Я описал её в AI Agent Evaluation in Production.
Измеряйте по одному изменению за раз
Абляция измеряет, вызывает ли компонент харнесса ожидаемый эффект: компонент изменяют или удаляют, а остальные условия эксперимента оставляют неизменными. Например, помогает ли editor linting этой модели на этом наборе задач?
Используйте следующий протокол:
- Зафиксируйте версию модели, экземпляры задач, окружение, грейдер и промпты, не относящиеся к тестируемому компоненту.
- Дайте обоим вариантам одинаковый общий бюджет токенов, времени и денег.
- До запуска сравнения выберите число испытаний или правило остановки.
- Запускайте одинаковые экземпляры задач в обоих вариантах. Поскольку выводы модели различаются, повторите каждую задачу несколько раз.
- Отчитайтесь о среднем значении вместе с разбросом или доверительным интервалом.
- Считайте каждое начатое испытание, включая тайм-ауты, остановки по политике, падения харнесса и сбои эвалуатора.
Один только success rate может скрыть дорогой компонент. Как минимум отслеживайте ошибочно принятые задачи, стоимость и wall time на завершённую задачу, ошибки инструментов, дублирующие заказы, минуты ревью и ручные переопределения разрешений. Выбирайте метрику, которая несёт реальную стоимость для вашего продукта. Рост числа завершённых задач на два процентных пункта — плохой обмен, если он вдвое увеличивает очередь на ревью.
Парный эксперимент с миграцией payment-адаптера делает handoff прогресса измеримым. Каждая пара control/treatment начинается с одного и того же коммита репозитория и seeded checkpoint, использует одну и ту же модель, задачу, грейдер и общий бюджет. Единственным переключателем является handoff. Основная метрика считает дублирующие tool actions после возобновления: действие считается дублирующим, если его операция и артефакт совпадают с шагом, который предыдущая сессия уже завершила.
В статье SWE-agent зафиксирована GPT-4 Turbo на 300 задачах из SWE-bench Lite; сообщается о 18,0% решённых задач для полного интерфейса по сравнению с 11,0% для агента только с shell, которому дали готовую демонстрацию, и 7,3% для того же агента без неё. Заявленный в статье разрыв в 10,7 процентного пункта измерен относительно baseline в 7,3%; часть 3 рассматривает те же три числа со стороны дизайна интерфейса. В статье также по отдельности изменялись функции интерфейса:
| Изменение интерфейса | Решено |
|---|---|
| Полный интерфейс SWE-agent (reference, без изменений) | 18,0% |
| Editor без linting | 15,0% |
| Полный файл вместо viewer на 100 строк | 12,7% |
| Полная история наблюдений вместо последних пяти | 15,0% |
Эти числа относятся к данной модели, бенчмарку и ограничению $4 на задачу. Три строки ниже reference — полезные тесты по одному признаку: в каждой изменялась одна функция интерфейса, а модель и схема эвалуации оставались фиксированными.
LangChain опубликовала более широкое сравнение при фиксированной модели для deepagents-cli. В нём сообщается о росте на Terminal-Bench 2.0 с 52,8% до 66,5%, при этом gpt-5.2-codex оставалась фиксированной, а команда меняла системный промпт, инструменты и middleware. В публикации объединено несколько изменений, но нет доверительного интервала, сравнения при фиксированном общем бюджете и таблицы абляций по каждому изменению. Поэтому результат не позволяет определить, какое именно изменение помогло. Названия моделей в этом разделе — те, которые фиксировались в соответствующих исследованиях; переносимым является протокол, а не список моделей.
Отчёт Anthropic о долго работающем приложении — это качественный, специфичный для продукта кейс, а не контролируемый бенчмарк. Приложение называется RetroForge и служит 2D-редактором ретро-игр; в Sprint 3 эвалуатор харнесса проверял 27 критериев, относящихся к редактору уровней. Работа начиналась на более ранних моделях Opus, а после выхода Opus 4.6 команда поочерёдно убирала компоненты харнесса, чтобы проверить, какие из них новая модель сделала ненужными. В отчёте говорится, что вызовы эвалуатора стали накладными расходами на задачах, которые Opus 4.6 надёжно выполняла самостоятельно, но по-прежнему помогали на границе возможностей модели. Этот пример показывает, почему при смене модели стоит заново проверять старый scaffolding; он не оценивает общий размер эффекта.
Оставляйте харнесс редактируемым после того, как он оправдал своё место
Абляция помогает держать харнесс небольшим, но его код всё равно может пережить модель, под которую его настраивали. Запрос вроде «маскируй секреты на каждом пути захвата» описывает поведение, а не файл. В production-харнессе это поведение может охватывать несколько стадий выполнения и общее состояние. Прежде чем безопасно изменить его, нужно найти каждое место реализации — и coding-агенту, которому вы делегируете эту работу, нужно сделать то же самое.
В препринте 2026 года Wang et al., Harness Handbook, этот поиск называется behavior localization. Handbook строит ориентированную на поведение карту кодовой базы харнесса. Статический анализ, не требующий вызовов модели, извлекает граф программы, а затем LLM группирует его элементы по стадиям выполнения.
Мейнтейнер или coding-агент начинает с общего обзора системы, открывает релевантную стадию выполнения и спускается к привязанным к исходному коду записям для функции или файла. Реестр состояния фиксирует, где общее состояние записывается и читается между стадиями. Такая иерархия сохраняет небольшой размер обзора, но оставляет путь к исходному коду.
Актуальность — отдельное правило. Каждый locator должен разрешаться относительно живого репозитория. Handbook замораживает устаревшие записи вместо того, чтобы угадывать, а каждый непустой diff повторно синхронизирует затронутые записи.
Диаграмма сжимает цикл изменения: запрос, описывающий только поведение, проходит по уровням Handbook; каждый кандидатный locator проверяется относительно живого репозитория до написания плана, а каждый применённый diff повторно синхронизирует карту.
В эвалуации Handbook используется протокол, который обосновывает эта статья. Она охватывает два open-source харнесса: Terminus-2 (шесть Python-файлов) и монорепозиторий Codex (2 267 Rust-файлов). В каждом случае read-only планировщик на базе DeepSeek-V4-Pro либо исследовал репозиторий напрямую, либо работал через Handbook. Запросы, репозиторий, разрешения инструментов и decoding были одинаковыми в обеих группах. Три джаджа (GPT-5.5, Opus 4.8, DeepSeek-V4-Pro) оценивали каждый план изменения по локализации, контролю области и ризонингу — важно, что один из джаджей совпадал с моделью, создавшей планы:
| Харнесс | Доля побед baseline | С Handbook | Токены планировщика |
|---|---|---|---|
| Terminus-2 (6 файлов) | 26,7% | 45,6% | −8,6% |
| Монорепозиторий Codex (2 267 файлов) | 28,3% | 38,3% | −12,7% |
Планировщик с Handbook чаще побеждал и использовал меньше токенов планировщика в обоих репозиториях. Но результат нужно рассматривать вместе с условиями: три LLM-джаджа оценивали планы изменений, созданные одной моделью-планировщиком, на двух харнессах. Исследование оценивало планы, а не применённые diff или production defect rate.
Проверьте метод в companion lab
Проект harness-demo на коммите 517353f3 — небольшое детерминированное упражнение с 12 общими синтетическими задачами, охватывающими такие изменения кода, как fix-parser-edge-case, split-large-module и wire-browser-test. В нём не реализован вымышленный репозиторий магазина.
Fixture каждой задачи задаёт сложность и четыре булевых условия: flaky-инструмент, потерянный прогресс, пропущенный участок реализации и неоднозначное завершение. Для сложных задач, которым также нужен файл прогресса, симулятор выводит пятое условие: без context_reset компрессия сохраняет устаревшие предположения. Детерминированный грейдер отмечает задачу как пройденную только тогда, когда выбранная конфигурация обрабатывает каждое применимое условие. Модель или внешний сервис не запускаются.
Команды отвечают на разные вопросы:
make checkзапускает Ruff и семь unit-тестов, включая валидатор, который отклоняет любую абляционную пару, изменяющую более одного компонента.make runвыводит накопительную teaching matrix, а затем пять корректных сравнений leave-one-component-out.make failuresназывает необработанное условие для каждой упавшей задачи. Полный харнесс должен завершиться сall synthetic tasks pass.
make check
make run
make failures
Causal section в make run выглядит так:
component control treatment delta
retry_policy 8/12 12/12 +4
progress_handoff 7/12 12/12 +5
evaluator 8/12 12/12 +4
fail_closed_acceptance 7/12 12/12 +5
context_reset 10/12 12/12 +2
Для каждой строки control — это полная конфигурация с удалённым одним компонентом; treatment восстанавливает только этот компонент. Предыдущая накопительная матрица полезна для ориентации, но некоторые соседние строки добавляют сразу несколько компонентов и поэтому не позволяют определить причину.
Lab валидирует каждую объявленную пару до её запуска. В regression-тестах также есть намеренно некорректная пара, одновременно меняющая политику ретраев и эвалуатор; валидатор её отклоняет.
При валидации пары lab сравнивает все пять полей компонентов. Этот исполняемый фрагмент показывает тот же guard на одной корректной паре с handoff прогресса:
from dataclasses import dataclass, fields
@dataclass(frozen=True)
class Config:
progress_handoff: bool = False
evaluator: bool = False
retry_policy: bool = False
fail_closed_acceptance: bool = False
context_reset: bool = False
def changed_components(control: Config, treatment: Config) -> tuple[str, ...]:
return tuple(
field.name
for field in fields(control)
if getattr(control, field.name) != getattr(treatment, field.name)
)
control = Config(progress_handoff=False, evaluator=True, retry_policy=True)
treatment = Config(progress_handoff=True, evaluator=True, retry_policy=True)
assert changed_components(control, treatment) == ("progress_handoff",)
Какой слой открывать, если запуск пошёл не так
Серия шла от внутренних слоёв к внешним — и здесь эта последовательность приносит пользу. У неудачного запуска агента обычно есть один ответственный слой:
| Что сделал запуск | Где находится исправление | Часть |
|---|---|---|
| Выбрал плохой следующий шаг, хотя нужная информация уже была перед ним | Цикл ризонинга или модель | 1 |
| Повторил работу или потерял решение, принятое час назад | Сборка контекста и handoff | 2 |
| Не смог выразить нужное действие или неверно прочитал возвращённый результат | Контракт инструмента | 3 |
| Сделал то, что вообще не должен был иметь возможность сделать | Правила разрешений | 4 |
| Потерял всё, когда worker завершился посреди вызова | Сессия, чекпоинт, сэндбокс | 5 |
| Объявил завершённой работу, которая не была выполнена | Проверки приёмки и трейсы | 6 |
Четыре из этих шести строк относятся к коду харнесса. Строка 5 — это лежащий под ним рантайм, а строка 1 — единственная, на которую ещё может повлиять промпт.
Начните с одного цикла и одной проверки приёмки
Я бы начал создавать харнесс coding-агента с одной способной моделью, инструкциями репозитория, несколькими узкими инструментами, сэндбоксом и одной явной проверкой приёмки. Я записывал бы tool calls, результаты, затраты и этот финальный тест в один трейс, чтобы первые полезные сбои были видны без восстановления картины по terminal-логам и чат-транскриптам. Это предлагаемая baseline-конфигурация, а не свидетельство из развёрнутой системы.
Дальше добавляйте только то, что оправдывает трейс. Фиксируйте, кто поддерживает каждый компонент, сколько токенов или секунд он добавляет и какой regression-тест оправдал бы его удаление после обновления модели.
Через шесть месяцев человек, который увидит progress_handoff=True, должен суметь найти трейсы сбоев, оправдавшие его добавление, и regression-кейсы, из-за которых он всё ещё нужен. Трейсы объясняют, зачем существует компонент; актуальная карта поведения объясняет, где его изменять.
Если вы попали сюда из поиска, пять предыдущих статей построили систему вокруг цикла ризонинга:
- Цикл выбирает следующий шаг.
- Память предоставляет контекст, а настоящий Postgres checkpoint store сохраняет его.
- Контракты инструментов определяют действия и формы результатов, которые могут читать последующие проверки.
- Безопасность добавляет deny hook и валидатор stop-hook. В примере оба остаются набросками, но обозначают точки контроля.
- Рантайм поддерживает процесс между сессиями и сбоями.
В серии также появился MCP-sidecar, показывающий, где должны находиться токены data provider, и evaluator node, который проверяет черновой отчёт до того, как его увидит человек. Это обычные фрагменты кода вокруг вызова модели. Роутер — код харнесса по той же причине: он выбирает паттерн ризонинга до запуска цикла ризонинга.
Следующий шаг — инструментировать один небольшой цикл. Записывайте tool calls, результаты, затраты и одну явную проверку приёмки. Добавляйте только один контроль после того, как трейс покажет сбой, на который он нацелен. Сравните его с фиксированной control-конфигурацией, а затем удалите, когда измеряемая польза исчезнет.
Ссылки
- OpenAI, Unrolling the Codex agent loop.
- OpenAI, Harness engineering: leveraging Codex in an agent-first world.
- Lopopolo, Harness engineering: anthology, field guide, and agent context bundle.
- Anthropic Engineering, Effective harnesses for long-running agents.
- Anthropic Engineering, Harness design for long-running application development.
- LangChain, Improving Deep Agents with harness engineering.
- Yang et al., SWE-agent: Agent-Computer Interfaces Enable Automated Software Engineering.
- Wang et al., Harness Handbook: Making Evolving Agent Harnesses Readable, Navigable, and Editable, arXiv:2607.13285, 2026.
- AWS, Making retries safe with idempotent APIs.
- Model Context Protocol, Tools specification.
- Market Analyst Agent Repository
Код Market Analyst Agent находится на GitHub.