# Модель ничего не помнит: как harness кодинг-агента меняет результат в 7.8 раза
Каноническая страница: https://plainews.ru/posts/razbirau-harness-koding-agenta-codex-opencode-pi
Опубликовано: 2026-07-19T07:01:09.774Z

Модель ничего не помнит — всю работу делает обвязка. Открыл исходники четырёх агентов и нашёл архитектурные ставки, которые меняют результат в 7.8 раза.
Модель stateless — она не помнит предыдущих шагов. Всю память, контекст и логику держит обвязка (harness). Она каждый ход собирает системные инструкции, историю и свежий ввод в один запрос, скармливает модели и разбирает, что делать дальше. И именно эта обвязка объясняет, почему один агент решает 55.4% задач на SWE-bench Pro, а другой с той же моделью внутри — только 45.9%.

Я собираю своего агента поверх OpenAI и столкнулся с узлами, которые базовый цикл не решает: как сжимать контекст, когда он распух, как не дать агенту повторять уже сделанное, как понять, что задача завершена. На каждом узле я шёл в исходники Codex, OpenCode, Pi и документацию Claude Code — и обнаружил, что агенты различаются не моделью, а архитектурными ставками обвязки. Причём ставки диаметральные: то, что один считает фундаментом, другой не реализует вообще.

## Почему обвязка важнее модели

Три числа с одной зафиксированной модели. Claude Opus 4.5 на стандартном scaffold от SEAL берёт 45.9% задач SWE-bench Pro. Тот же Opus, та же модель, но обвязка от Claude Code — уже 55.4%. Плюс 9.5 процентных пункта за счёт одной только обвязки.

На SWE-bench Verified Mini разрыв ещё грубее: Sonnet 4.5 в scaffold от SWE-Agent решает 68% задач, а в generalist-scaffold от HAL — те же 34%. Половина результата держится не на модели, а на том, как вокруг неё устроено.

Самое сильное число — контролируемый эксперимент на 100 задачах SWE-bench Verified. Меняли по отдельности модель и обвязку, считали дисперсию. Разброс результата от смены обвязки оказался в 7.8 раза больше, чем от смены модели.

Есть встречная работа от IBM Research (arXiv 2602.22953): на пяти универсальных архитектурах агента поверх пяти моделей модель объясняет 27.8% разброса в качестве, а архитектура агента — всего 0.5%. Разница больше чем в 50 раз, и в другую сторону.

Отсюда правило: **на универсальной обвязке решает модель, а на вылизанной под задачу решает обвязка**. Мой agent-core пока ближе к универсальному краю, и это ограничивает потолок.

## Что такое harness и что в него входит

Формулу Agent = Model + Harness ввёл Вив Триведи из LangChain. Harness — это всё, что не модель:

- Системные промпты
- Инструменты, скиллы, MCP-протокол
- Встроенная инфраструктура: файловая система, песочница, браузер
- Оркестрация и агентный цикл
- Хуки и middleware: сжатие контекста, продолжение, линтинг

У слова два смысла: широкий, продуктовый (вся обёртка вокруг модели, так говорят Anthropic и OpenAI), и узкий, архитектурный (только цикл исполнения). В документации Claude Code сказано прямо: "Claude Code is the harness; Claude is the model inside it".

Сердце любой обвязки — агентный цикл. Anthropic описывает его как три фазы: gather context, take action, verify results. Но дьявол в том, кто пишет диспетчер этого цикла и что он в него докладывает сверху.

## Как устроен базовый цикл и где начинаются различия

Pydantic AI, на котором стоит мой agent-core, даёт граф из трёх узлов: узел пользовательского ввода, узел запроса к модели, узел вызова инструментов. Можно было бы отдать управление графу и не думать. Я отдавать не стал и обернул его в собственный цикл с явной диспетчеризацией:

``python while not isinstance(node, End):     if isinstance(node, UserPromptNode):         node = await agent_run.next(node)         continue     if isinstance(node, ModelRequestNode):         # здесь контроль перед запросом к модели         node = await agent_run.next(node)         continue     if isinstance(node, ToolCallNode):         # здесь контроль перед вызовом инструмента         node = await agent_run.next(node)         continue ``

Мне нужен был контроль над каждым переходом: чтобы вклиниться перед запросом к модели, проверить инструмент перед вызовом, сжать контекст при необходимости.

Именно тут начинаются архитектурные ставки. Codex, OpenCode и Pi все пишут свой цикл поверх фреймворка. Но что они в него закладывают — это уже разные миры.

## Архитектурные ставки, которые различаются диаметрально

### Сжатие контекста

**Codex** не сжимает контекст вообще. Ставка на то, что контекстное окно моделей растёт быстрее, чем агент успевает его заполнить. Если контекст переполнился, агент просто останавливается с ошибкой.

**OpenCode** использует скользящее окно: удаляет старые сообщения по FIFO, оставляя системный промпт и последние N ходов. Код простой:

``python if len(messages) > max_messages:     messages = [messages[0]] + messages[-(max_messages - 1):] ``

**Pi** делает семантическое сжатие: прогоняет историю через модель с промптом "summarize this conversation so far", заменяет старые сообщения саммари. Дороже по токенам, но сохраняет контекст лучше.

**Мой agent-core** пока использует гибридный подход: сначала скользящее окно для быстрых задач, потом семантическое сжатие, если задача затягивается.

### Детекция завершения

**Codex** полагается на то, что модель явно вернёт финальный ответ. Если она этого не сделала за N итераций, цикл останавливается по таймауту.

**OpenCode** добавляет heuristic: если модель два хода подряд не запросила инструмент и вернула текстовый ответ, считаем задачу завершённой.

**Pi** идёт дальше: после каждого хода без вызова инструмента отправляет модели дополнительный запрос "are you done?" и парсит ответ.

**Claude Code** (из документации) использует внутреннюю метрику уверенности: модель возвращает не только ответ, но и скор завершённости. Если скор выше порога, задача считается готовой.

### Обработка ошибок инструментов

**Codex** при ошибке инструмента возвращает модели сырой traceback и даёт ещё одну попытку.

**OpenCode** парсит ошибку и формирует человекочитаемое сообщение с подсказкой, что пошло не так.

**Pi** при ошибке файловой операции автоматически пробует альтернативные пути: если не удалось прочитать файл по относительному пути, пробует абсолютный.

**Мой agent-core** возвращает структурированную ошибку с полями error_type, message, suggestion. Модель видит не traceback, а понятное описание и рекомендацию, что попробовать дальше.

### Дедупликация действий

**Codex** не следит за повторами. Если модель дважды попросила прочитать один файл, обвязка прочитает дважды.

**OpenCode** ведёт список уже выполненных операций и при повторном запросе возвращает закешированный результат.

**Pi** добавляет превентивную проверку: если модель запросила действие, которое уже было выполнено, обвязка возвращает не результат, а сообщение "you already did this, here's what you got".

## Где это ломается и что важно учесть

Вылизанная обвязка — это техдолг. Каждая новая модель приносит новые способности, и часть костылей в harness становится лишней. Sonnet 4 лучше следит за контекстом, чем Sonnet 3.5, и часть логики сжатия уже можно выкинуть.

Универсальная обвязка проигрывает заточенной под задачу. Если вы делаете агента для одной узкой задачи (например, только для рефакторинга), можно закладывать специфичные ставки и выигрывать 10-20 процентных пунктов. Если делаете продукт общего назначения, большая часть специфичных оптимизаций не выстрелит.

Числа протухают быстро. Terminal-Bench 2.0 за полгода уехал с 62.9% в январе до 84.7% к лету. Не цепляйтесь к абсолютным значениям — важна пропорция, а не конкретный процент.

## Что попробовать дальше

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

Читайте исходники. Codex (версия 0.144.5), OpenCode (1.18.3), Pi (0.80.10) — все лежат открыто. Вы увидите не только готовые решения, но и места, где они намеренно чего-то не делают.

Фиксируйте версии. Эксперименты с harness имеют смысл только на зафиксированной модели. Если модель обновилась, половина оптимизаций может стать лишней или, наоборот, критически важной.

И главное: не верьте присказкам. Проверяйте цифры на своих задачах. На универсальной обвязке решает модель, на вылизанной — обвязка. Где вы находитесь на этой шкале, покажут только измерения.

## Источники

- Habr AI: Harness кодинг‑агента: разобрал исходники Codex, OpenCode, Pi и свою собственную
