Harness Bench: как оценить агентский harness и выбрать связку с моделью

от автора

Привет! Я Андрей Иванов, NLP-исследователь в R&D-лаборатории red_mad_robot.

Когда мы собираем AI-агента, первым делом выбираем модель под задачу. Но в реальном приложении она не работает в одиночку, ей нужен агентский harness — программная обвязка. Поэтому выбирать приходится не просто модель, а связку «модель + harness».

Чтобы делать этот выбор осознанно, мы создали Harness Bench — открытый фреймворк, который тестирует связки на реальных задачах в одинаковых условиях. В статье расскажу, как он устроен, разберу баги опенсорсных обвязок, которые ломают автоматический прогон, а потом покажу на цифрах, как смена harness влияет на способности одной и той же модели.

Проблема: модель больше не весь инструмент

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

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

IT-сообщество только приходит к тому, что harness нужно тщательно тестировать:

  • В мае 2026 года появился проект WildClawBench. Его цель — отделить интеллект модели от качества агентской обвязки.

  • В свежих гайдах Anthropic подчеркнули, как важно оценивать агента в реальной среде, где он будет ошибаться и самостоятельно восстанавливать контекст.

  • Создатели SWE-bench выпустили отдельную версию SWE-bench Verified. Выяснилось, что одна и та же модель выдаёт очень разные результаты в зависимости от harness, в который она обёрнута.

Подойдут ли нам привычные бенчмарки? Стандарты вроде SimpleQA для поиска фактов в интернете или SWE-bench для программирования создавались под completion-модели. Они жёстко привязаны к статичному формату и не рассчитаны на автономный многошаговый цикл.

При этом отказываться от стандартов не стоит, ведь у них богатая история тестирования, да писать новые бенчмарки с нуля под каждый фреймворк — тоже не выход. Нам потребовалось решение, которое позволит брать привычные бенчмарки и прогонять их через любые связки «модель + harness» в воспроизводимых условиях.

Архитектурный каркас Harness Bench

При тестировании агентов мы столкнулись с проблемой: harness-обвязки, логика задачи и правила оценки развиваются с разной скоростью. Если они жёстко связаны, при интеграции нового бенчмарка приходится переписывать половину системы.

Мы отказались от монолита в пользу гибкой архитектуры, вдохновившись подходом Inspect AI. Каркас держится на трёх принципах:

  • Независимые оси: компоненты не знают друг о друге и стыкуются только при запуске.

  • Инфраструктура как данные: окружение задачи описано прямо в её данных, а не настраивается вручную.

  • Жёсткая изоляция сред (Docker-out-of-Docker): всё работает в отдельных контейнерах, а агент получает доступ к рабочей среде только через MCP.

Парадигма трёх осей

Мы разложили систему на три независимые оси. Каждая отвечает на свой вопрос: кто выполняет задачу, что именно выполняется и как это оценивается.

  1. Harness (кто выполняет): получает сообщения от оркестратора, доставляет их агенту и возвращает ответ. Суть задачи ему неизвестна.

  2. Benchmark (что выполняется): готовит задачи и разворачивает под каждую своё окружение. Кто будет их решать — не имеет значения.

  3. Scorer (как оценивается): берёт ответ агента и состояние среды, а затем выносит вердикт — CORRECT, PARTIAL или INCORRECT.

Оси полностью изолированы: Benchmark не может импортировать Harness, а Scorer ничего не знает о внутренностях модели. Они пересекаются только в момент запуска, поэтому любую модель, задачу и способ оценки можно свободно комбинировать.

Инфраструктура как данные

Окружение задачи мы не настраиваем вручную — оно целиком описано в самой задаче. Каждая из них содержит не только вопрос с эталонным ответом, но и описание своей среды: какой Docker-образ запустить, какие инструменты дать агенту через MCP, какими проверками оценить результат: LLM-as-a-judge, bash-проверкой в контейнере или точным совпадением.

Поэтому две задачи из одного бенчмарка могут работать в совершенно разных средах. Бенчмарк только готовит данные, а окружение под них разворачивает оркестратор.

Многоконтейнерная изоляция (DooD)

Архитектура стенда. Orchestrator ведёт весь цикл: читает конфигурацию из Benchmark, поднимает и останавливает контейнеры, сохраняет результаты в PostgreSQL. Агент работает в своём контейнере и достаёт до песочницы (SandBox) только через MCP-сервер, который переводит вызовы инструментов (Tools) в команды внутри песочницы. Scorer проверяет результат, пока песочница ещё работает.

Архитектура стенда. Orchestrator ведёт весь цикл: читает конфигурацию из Benchmark, поднимает и останавливает контейнеры, сохраняет результаты в PostgreSQL. Агент работает в своём контейнере и достаёт до песочницы (SandBox) только через MCP-сервер, который переводит вызовы инструментов (Tools) в команды внутри песочницы. Scorer проверяет результат, пока песочница ещё работает.

Агентские harness-фреймворки — тяжёлые системы со своими зависимостями. Держать их в одном процессе с нашим кодом нельзя, поэтому каждый запускается в отдельном Docker-контейнере. Получается схема Docker-out-of-Docker — наш код сам работает в контейнере и запускает изнутри другие.

Под каждую задачу оркестратор разворачивает три части:

  • Sandbox — рабочую среду задачи в отдельном контейнере: репозиторий, базу данных, файлы.

  • Контейнер с агентом — сам harness.

  • MCP-сервер на хосте — мост между ними. Он запускается на случайном порте, и его URL передаётся в контейнер агента.

Агент не работает с песочницей напрямую. Он отправляет через обычные JSON-RPC-запросами вызовы инструментов на MCP-сервер, а тот переводит их в безопасные команды docker exec внутри песочницы. Так агент даже не знает, что работает с Docker, а песочница ничего не знает об агенте.

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

Бенчмарки: не данные, а живые среды

Классический бенчмарк статичен. Это либо набор пар «вопрос — ответ», либо заранее зафиксированная среда, где модель только описывает действие текстом, а выполняет его сам бенчмарк.

Агенту нужна интерактивная среда: он должен вызвать инструмент, получить ошибку, прочитать логи, переписать код и попробовать снова. Поэтому наш Harness Bench — не просто хранилище данных, а генератор изолированных песочниц.

Бенчмарк — это лёгкий класс-адаптер. Сам он задачу не выполняет, лишь готовит данные и указывает оркестратору, как их оценивать. Благодаря этому добавить свой бенчмарк просто — достаточно написать один класс с тремя методами, как в примере в коде.

from framework.benchmarks.base import Benchmark, register_benchmarkfrom framework.models import Sample, SandboxSpec@register_benchmark('my_bench')class MyBenchmark(Benchmark):    name = 'my_bench'    def load_samples(self) -> list[Sample]:        """Загружает задачи и формирует инфраструктуру для каждой"""        pass    def make_scorer(self) -> Scorer:        """Возвращает механизм оценки (LLM-судья, bash-чекпоинты и др.)"""        pass    def make_runner(self) -> Runner:        """Определяет протокол диалога (по умолчанию SingleTurn)"""        pass      

Главный метод — load_samples(). Он возвращает список объектов Sample, где каждый объект — это одна задача с текстом вопроса и эталонным ответом. Рядом лежит набор инструментов, доступ в интернет и Docker-образ под задачу. Это и есть «инфраструктура как данные» в действии: окружение задачи лежит прямо в её описании.

Стратификация датасетов: как не сжечь бюджет на API

Запуск агентов обходится дорого. По результатам наших тестов, одна задача SWE-bench — это в среднем 360 тысяч токенов (у тяжёлых задач их больше миллиона) и 10–15 минут работы агента. Если прогнать тысячи задач на больших моделях, бюджет быстро закончится.

Чтобы оптимизировать затраты, мы применили стратегию стратифицированной выборки: кратно уменьшили объём датасетов, строго сохранив репрезентативность по сложности и категориям.

Наш итоговый пул задач:

  • Tool Calling (BFCL) — 90 многоходовых задач. 

  • Программирование (SWE-bench) — 12 на Python + 8 на других языках (Go, Rust, Java, C++).

  • Кодогенерация (HumanEval+) — 164 задачи, весь датасет.

  • Анализ данных (TheAgentCompany) — 10 сценариев для разных ролей. 

  • Web Search (SimpleQA) — 50 вопросов, по одному на тему. 

  • Личный ассистент (BitGN PAC1) — 43 задачи: работа с файлами и защита от prompt injection. 

  • Агентская память (BFCL Memory) — 30 вопросов на память между сессиями.

Адаптация: от текста к динамическим средам

Собрать задачи — только половина дела. Оригинальные датасеты создавались под completion-модели, поэтому нам пришлось переработать их дизайн для работы в динамическом цикле. Расскажу, что же мы изменили.

Berkeley Function Calling Leaderboard (BFCL)

В оригинале модель просто пишет вызов текстом, например [func_name()]. Агент так не работает — ему нужно вызвать инструмент и получить ответ. Поэтому между задачей и агентом мы поставили MCP-сервер с набором mock-сервисов. Агент вызывает инструмент — сервис выполняется и сохраняет своё состояние между вызовами.

Один и тот же результат можно получить разными путями. Поэтому к строгой проверке вызовов мы добавили LLM-as-a-judge. Если тест ждал find("tmp/File.txt"), а агент сделал cd("tmp") и find("./File.txt"), судья засчитает это как верное решение, ведь семантически это одно и то же.

SWE-bench и TheAgentCompany

В оригинальном SWE-bench модель получает задачу текстом, например, описание проблемы из GitHub-issue и сразу пишет патч, ничего не запуская. Мы от этого отказались. 

Теперь под каждую задачу оркестратор разворачивает настоящую среду: для SWE-bench это контейнер с репозиторием, для TheAgentCompany — docker-compose с базой данных и Git-сервером. Агент сам ходит по коду, запускает его и ловит ошибки в реальном времени через тот же MCP-мост. Оценка идёт прямо в работающем контейнере — мы запускаем тесты репозитория или проверочные скрипты на месте.

BFCL Memory

Это выделенное нами из BFCL подмножество для проверки агентской памяти. Мы проверяем, умеет ли агент запоминать факты в одной сессии и пользоваться ими в другой. Здесь важен не ответ сам по себе, а откуда агент его берёт.

Тестирование идёт в два этапа. Сначала серия сессий, где агент узнаёт факты, но контекст между сессиями мы намеренно обрываем. Каждый запрос стартует с чистого листа, опереться на окно контекста не выйдет — только на собственную память агента. На втором этапе мы запускаем новую сессию исключительно с вопросами. Ответить агент должен сам, без штатных инструментов бенчмарка, полагаясь только на то, что запомнил.

BitGN PAC1

Исходный бенчмарк работает по кастомному gRPC API. Мы написали адаптер: перед стартом система загружает рабочее пространство из удалённого PCM sandbox и монтирует его локально для агента, а после работы синхронизирует изменения обратно для оценки.

Такая переработка превратила статические тексты в стресс-тесты для проверки Agent Loop — способности агента планировать шаги и самостоятельно восстанавливаться после ошибок.

Харнессы в суровой реальности: баги, скрытые таймауты и сожжённые токены

Каждый harness для нас — чёрный ящик. Всю агентскую работу: планирование, вызовы инструментов, восстановление после ошибок он делает сам, внутри своего контейнера. С нашей стороны только тонкий адаптер — он получает задачу, доставляет её в контейнер обвязки и возвращает ответ.

Для тестов мы взяли три популярных опенсорсных фреймворка: Hermes, OpenClaw и OpenCode. Все они рассчитаны на живого пользователя рядом: он ведёт диалог, отвечает агенту и подтверждает действия. Стоило запустить их массово и без человека — и начались проблемы. Одни возникли из-за расчёта на пользователя, другие из-за жёстко заданных лимитов, третьи оказались обычными багами.

OpenClaw: ритуалы знакомства

OpenClaw сильнее других заточен под живого пользователя — он буквально хочет сначала «познакомиться». В автоматическом прогоне это, вместе с парой жёстких базовых настроек, доставило нам хлопот. Мы столкнулись с тремя проблемами.

Первое — онбординг. При первом запуске агент ждёт, что пользователь расскажет о себе: в системный промпт вшит запрет браться за рабочие запросы, пока не пройден этап знакомства BOOTSTRAP.md. В автопрогоне бенчмарка возникает конфликт, когда оркестратор ждёт от агента решения задачи, а агент — ответа от несуществующего пользователя. Это решается флагом skipBootstrap: True при старте.

Второе — таймаут на вызов инструмента. В SWE-bench агенту нередко требуется время на выполнение долгой команды, например, поставить пакет через pip install -e . или прогнать тесты. А OpenClaw обрывает ожидание ответа ровно через 60 секунд. Агент получает ошибку, запускает команду заново, и в песочнице начинают мешать друг другу два процесса. Причём лимит зашит даже не в самом OpenClaw, а в базовых настройках MCP SDK, поэтому через конфигурационные файлы его не изменить — пришлось патчить исходники прямо в Docker-образе.

Третье — стриминг, который нельзя выключить. На длинных ответах поток между провайдером и агентом нередко прерывался на полпути, и проще было бы вообще отключить стриминг. Но это не работает. В конфиге есть поле stream — казалось бы, выстави его в false, и всё. На деле в коде запроса к LLM stream: true прописан жёстко и игнорирует любые настройки.

OpenCode: как незаметно сжечь море токенов

В метриках OpenCode сразу бросился в глаза избыточный расход входных токенов — больше, чем требовали сами задачи.

Причина оказалась неожиданной. Для каждой новой сессии OpenCode придумывает красивый заголовок. Ради этой единственной строчки он отправляет отдельный фоновый запрос к модели, прикладывая к нему весь системный промпт и полный контекст задачи. На сотнях задач набегало до 10 тысяч лишних токенов просто так. Мы отключили эту функцию одной строкой в конфиге opencode.jsonc: {"agent": {"title": {"disable": true}}}.

Hermes: обрывы потока на длинных ответах

Главная проблема с Hermes — стриминг. При работе через провайдеров, например, OpenRouter, на объёмных задачах ответ часто прерывался на полпути — балансировщик закрывал соединение посреди длинного ответа, скажем, пока агент писал патч. Агент получал обрезанный текст, терял контекст и проваливал задачу. Проблема массовая — на GitHub можно найти десятки issue.

Надёжнее всего было бы выключить стриминг, чтобы ответ от OpenRouter приходил агенту целиком и прерываться было нечему. Но через конфиг этого не сделать, потому что флаг display.streaming: false отключает лишь вывод в консоль, а API-шлюз всё равно отдаёт ответ потоком. Пришлось патчить Docker-образ и убирать стриминг прямо в коде.

Эксперименты: что показала практика

После стабилизации инфраструктуры мы провели 12 полных циклов тестирования: три harness-фреймворка и четыре разные LLM. Цель — не просто измерить интеллект моделей в вакууме, а понять, как архитектура конкретного агента влияет на итоговый результат.

Конфигурация стенда:

  • Облачный контур NeuralDeep Hub: qwen3.6-35b-a3b и gpt-oss-120b.

  • Облачный контур OpenRouter API: minimax-m2.5 и kimi-k2.6.

Все запуски проводились строго на пропатченных Docker-образах, чтобы системные ошибки фреймворков не искажали метрики.

Результаты прогонов harness + LLM на бенчмарках фреймворка

Результаты прогонов harness + LLM на бенчмарках фреймворка

Анализ собранных данных выявил три инсайта.

Объём параметров больше не гарантирует качество

Зафиксируем harness — пусть везде будет Hermes — и станем менять только модель. Результат ломает интуицию: модель на 35 млрд параметров уверенно обходит 120 млрд и в починке багов (SWE-bench), и в корпоративных задачах (TheAgentCompany). Причём не на проценты, а в разы.

Большая модель хороша там, где нужен длинный контекст и простая генерация кода. Но как только ей приходится самой вызывать инструменты и исправлять ошибки в песочнице, размер перестаёт помогать. Решает не объём, а умение вести длинный диалог — планировать на несколько шагов вперёд и восстанавливаться после ошибок.

Харнесс влияет на результат не меньше, чем модель

Это главное подтверждение гипотезы, ради которой создавался Harness Bench: оценивать нейросеть в отрыве от агентской обвязки бессмысленно. Одна и та же модель показывает совершенно разные результаты в зависимости от того, какой фреймворк ею управляет.

Показательный пример — результаты minimax-m2.5 на бенчмарке TheAgentCompany для комплексного анализа данных и работы с инфраструктурой:

  • Под управлением Hermes модель показала 83,2%.

  • Под управлением OpenCode модель выдала 55,6%.

  • Под управлением OpenClaw результат упал до 46,2%.

Разница почти в два раза обусловлена исключительно тем, как harness парсит ответы, управляет историей сообщений и маршрутизирует вызовы инструментов.

Ещё один пример — работа с памятью (BFCL Memory). Агент должен опираться на факты из предыдущих сессий. Hermes с этой задачей справился — GPT 120B набрал 53,3%, а OpenCode получил 0% на всех моделях без исключения. Дело не в модели: архитектура OpenCode просто не поддерживает агентскую память. Это лучшая иллюстрация того, почему harness нельзя брать «по умолчанию» — его нужно тестировать под конкретную задачу.

Лидеры сложных рассуждений

Крупные модели ожидаемо лидируют в сложных категориях, но единого победителя нет, их профили различаются. Kimi-k2.6 (с Hermes) — про поиск и код — первое место в SimpleQA и лучший результат в починке багов на Python. Minimax-m2.5 силён в вызове функций, анализе данных и роли личного ассистента: лучшая точность на BFCL, TheAgentCompany и PAC1. У одной модели лучшие результаты получаются с разными обвязками.

Вывод: тестируйте связку, а не модель

Мы начинали с вопроса о том, какую связку «модель + harness» взять под задачу. Тесты дали ясный ответ: обвязка влияет на результат не меньше самой модели. Из этого следуют три вывода:

  1. Размер модели не гарантирует качество. Модель на 35B обходит 120B там, где надо планировать и исправлять ошибки.

  2. Harness решает многое. Например, OpenCode не умеет держать память между сессиями, и никакая мощная LLM этого не починит.

  3. Единого победителя нет. В разных задачах выигрывают разные связки.

Есть и чисто инженерная сторона. Готовые harness-фреймворки плохо приспособлены к автоматическому прогону: немало времени ушло не на сами эксперименты, а на то, чтобы всё стабильно заработало. Мешали жёсткие таймауты, обрывы стриминга, лишний расход токенов. Это тоже часть честной оценки: если связку не удаётся нормально запустить на ваших задачах, лучше узнать об этом заранее.

В Harness Bench вы можете протестировать любую модель на наших задачах или добавить свой бенчмарк — для этого достаточно написать один Python-класс. Всё остальное уже настроено: фреймворки (Hermes, OpenClaw, OpenCode), Docker-изоляция, инструменты через MCP, Web UI и генерация отчётов.

Удачных тестов!


Над материалом работали:

Текст — Андрей Иванов

Редактура — Игорь Решетников

Иллюстрации — Саша Буяк


Это статья от команды R&D. Если интересно больше исследований, экспериментов и инженерных деталей — заглядывайте в наш канал: t.me/rmr_rnd

А в основном канале red_mad_robot — новости, анонсы и всё, что происходит вокруг компании: t.me/redmadnews

ссылка на оригинал статьи https://habr.com/ru/articles/1053950/