Пять уровней агента-харнеса с примерами кода, который не будет работать без них.
Пять слоев механизма управления агентом, каждый из которых содержит код, неспособный функционировать без соответствующего слоя: граница выполнения, сандбоксирование, память, циклы проверки и конвейер контекста.
Источники: `The Complete Guide to Agent Harnesses (With Code)`
Представьте команду, которая добавляет одну строку в конфигурацию агента: никакие изменения не вносятся без подтверждения человека. Формулировка ясна, строка расположена близко к верху, и агент загружает её в начале каждой сессии.
Но однажды днём одна сессия одиннадцать раз превысила установленный лимит и включила около семидесяти шести неподтверждённых изменений в итоговый документ для клиента. Ошибок не появилось. Ничто не остановилось для проверки. Через два часа кто-то просмотрел документ, выделив каждую строку, которую агент изменил самостоятельно, и нашёл сорок шесть таких строк в файле с именем клиента.
Само указание было в порядке. Оно просто оказалось не в том месте.
Вместо этого работает хук — небольшой скрипт, который запускается инструментальной средой непосредственно перед отправкой запроса к инструменту. Он анализирует ожидающийся запрос и может отклонить его. Агент всё ещё может захотеть внести изменения, но они так и не попадут в файл. Такой хук иногда может давать неверные результаты, обычно из-за слишком узкого формулирования правила, и преимущество заключается в том, что можно открыть код и указать точное условие, из-за которого произошла ошибка. В промпте же фраза не позволяет указать конкретное место проблемы.
Разница между объявленным правилом и реализуемым правилом — это сама инструментальная среда. Она охватывает пять областей:
- Действия, которые может выполнить агент
- Места, до которых он может добраться
- Состояние, сохраняющееся после перезагрузки
- Человек, проверяющий его результат
- Стоимость того, что ему отображается
Каждой области присваивается соответствующий код. Существует набор рабочих демонстраций, в которых каждый слой тестируется на собственном ноутбуке ещё до того, как он может выйти из строя в производственной среде. Это предназначено для тех, кто уже выпустил агента и столкнулся с его сбоями.
Что на самом деле даёт вам такой инструмент
Начнём с чего-то настолько очевидного, что его трудно даже сформулировать. Основной цикл кодингового агента состоит из шести строк:
while agent.turns < max_turns:
call = agent.next_call(observations)
if call is None:
break
result = execute(call)
observations.append(result)
Эти строки представляют собой агента. Благодаря обработке ошибок и повторным попыткам число может достичь почти двадцати. Любая оцененная вами фреймворк-среда оборачивает этот цикл, причем наиболее важной частью является функция execute. Эта функция определяет, что может делать модель, с чем она может взаимодействовать, что она сохраняет, кто осуществляет её аудит и что в итоге попадает к ней.
Все эти решения вместе составляют основу работы системы, и многие комментарии утверждают, что они имеют большее значение, чем выбор самой модели. Однако данные подтверждают более ограниченное утверждение, которое, к счастью, более практично.
По стоимости преимущество у harness-решений. Предварительная публикация за июнь 2026 года сравнила три типа harness-решений (Goose, OpenCode, OpenHands-SDK) на двух моделях с использованием стратифицированного набора из 50 задач Terminal-Bench Pro — более сложной версии набора инструментов Terminal-Bench. Количество токенов, потребляемых на решение одной задачи, различалось между harness-решениями в среднем в 40 раз. Переход на более совершенную модель изменял это значение всего на 1,0–1,3 раза.
По функциональным возможностям преимущество не на их стороне. Уровень успешного выполнения задач различался между harness-решениями на 0–8 пунктов, и с учетом доверительных интервалов значения почти всех случаев, кроме тех с наибольшей разницей, находились в пределах нуля. Сами авторы называют результаты точности описательными при n=50.
Проще говоря, одна задача, выполненная одной моделью с определённым уровнем успеха, может стоить в сорок раз дороже при использовании одного инструментария по сравнению с другим. Все последующие меры направлены на снижение затрат и уменьшение неожиданностей, причём ни одна из них не добавляет интеллекта слабой модели.
Перенос правил в код
Формулировка правила в промпте системы равносильна ставке на то, что модель вспомнит его, интерпретирует правильно и будет ему следовать даже при наличии множества контекстных данных. Обычно модель побеждает. Именно этот уровень успеха делает ситуацию рискованной, поскольку люди теряют желание проверять её действия.
Правила на уровне кода срабатывают при каждом вызове, даже если модель уже забыла о них.
В цикле агента есть пять мест, где код может остановить выполнение. Каждый из разделов ниже описывает его назначение, код, обеспечивающий соблюдение правил, и поведение при отсутствии соответствующего слоя. Никому не нужны все пять слоев сразу. Выберите тот слой, чья неисправность, по вашим представлениям, может возникнуть у вас на этой неделе с наибольшей вероятностью.
Слой 1: граница выполнения
Что разрешено агенту делать?
Эта граница представляет собой функцию, расположенную между выбором модели и фактическим вызовом инструмента. Начните с агента без дополнительных ограничений. В его инструкциях указано, что файлы нельзя удалять без предварительного запроса, и в очереди ожидают три вызова инструментов.
SYSTEM_PROMPT = "IMPORTANT: never delete a file without asking the user first."
def execute(call):
if call.name == "delete_file":
FILES.pop(call.args["path"], None)
return f"deleted {call.args['path']}"
При запуске агент удаляет cache.tmp — бесполезный мусор, а затем удаляет client-deliverable.md, что соответствует его названию. Вопросов не задавалось, хотя в инструкции с самого начала было указано это правило.
Функция, размещённая перед инструментом, решает эту проблему:
def boundary(rules):
def wrap(execute):
def guarded(call):
for name, deny_if, reason in rules:
if deny_if(call):
return f"DENIED by {name}: {reason}"
return execute(call)
return guarded
return wrap
NEEDS_APPROVAL = [(
"delete-needs-approval",
lambda c: c.name == "delete_file" and not c.args.get("approved_by_human"),
"deletion requires an explicit human approval flag on the call",
)]
При использовании того же агента, скрипта и запроса ничего не удаляется.
Уровень 2: песочница
До чего может добраться агент?
Песочница определяет, к каким каталогам и хостам может обращаться процесс агента. Её можно создать с нуля: начать с пустоты и предоставить только то, что необходимо для выполнения задачи, с помощью списка разрешённых путей, списка разрешённых хостов и процесса, не осведомлённого о всём остальном.
Распространённой альтернативой является список запрещённых путей:
DENY = ["secrets/"]
def execute(call):
path = call.args["path"]
if any(path.startswith(d) for d in DENY): # checks the spelling
return "DENIED by deny-list"
real = os.path.normpath(path) # the ../ collapses HERE, after the check
return DISK.get(real, "not found")
Запустите его, и агент попытается дважды:
read('secrets/api_key') -> DENIED by deny-list
read('work/../secrets/api_key') -> sk-live-DO-NOT-LEAK
Проверка анализировала текст в том виде, в котором он был введен, тогда как поиск файла осуществлялся по местоположению, к которому относится этот текст. Два запроса, два варианта написания — один файл.
Для устранения проблемы сначала необходимо нормализовать путь, а затем сравнить его:
ALLOW_ROOTS = ["work"]
def resolve(path):
real = os.path.normpath(path) # resolve FIRST
if not any(real == r or real.startswith(r + os.sep) for r in ALLOW_ROOTS):
return None
return real
Оба варианта написания теперь сводятся к secrets/api_key, который находится не под разрешённым корневым уровнем, поэтому оба варианта отклоняются. Здесь важно разрешать, а не запрещать, поскольку список запретов блокирует только те варианты написания, которые представил его создатель.
Эти примеры представляют собой обычные функции Python, поэтому для их тестирования не требуется контейнер. В реальной среде развертывания аналогичная проверка осуществляется самими границами процесса: пользователь или контейнер, у которых нет прав на чтение за пределами папки work/, выполняют операции в том же порядке.
Слой 1 не может выполнять эту задачу. Проверка, находящаяся внутри вашей программы, видит только те вызовы, которые она распознаёт, тогда как команда оболочки, символическая ссылка или путь с элементом .. могут обратиться к тому же файлу, не являясь ни одним из этих вызовов.
Слой 3: сохранение в памяти
Что остаётся после сброса контекста?
Одна привычка приносит больше пользы, чем большинство других: прерывание разговора без долгих раздумий. Содержимое диалога накапливается, качество снижается, и агент начинает противоречить сам себе. Многие продолжают вести такие сессии. Лучше начинать заново раньше, если важная информация сохраняется в месте, которое не будет стерто при перезапуске — будь то зафиксированный код, конфигурация, сама сборка или краткие заметки.
Поэтому целенаправленно определите, где должна находиться каждая часть состояния. Существует три варианта: в самом диалоге, где он исчезает вместе с сессией; на диске, где он сохраняется и может быть восстановлен позже; и в конфигурации сборки, где он влияет на все последующие сессии.
Одна функция обработки задач покрывает все три варианта:
CONVERSATION, DISK, HARNESS_CONFIG = [], {}, {}
def remember(kind, key, value):
"""'chat' dies with the session, 'disk' survives it,
'config' shapes every session that follows."""
{"chat": lambda: CONVERSATION.append(value),
"disk": lambda: DISK.__setitem__(key, value),
"config": lambda: HARNESS_CONFIG.__setitem__(key, value)}[kind]()
Эта функция практически бесполезна. Всё, что она даёт, — это правило, согласно которому ничего не сохраняется без указания, в одно из трёх хранилищ это должно попасть. Команды редко делают этот выбор явно, что объясняет, почему агенты иногда забывают о чём-то, а по другим вопросам упорно ошибаются. В демонстрации список разговоров остаётся пустым, в то время как решение и правило сохраняются на диске и в конфигурации.
Уровень 4: циклы проверки
Кто проверяет результаты, и что мешает проверяющему просто всё исправить?
Цикл проверки означает повторный анализ работы агента, выполняемый тем, кому разрешено фиксировать проблемы, но не устранять их.
Вторая половина — это то, что люди обычно пропускают. Пусть вспомогательный агент рецензента выполняет работу, и он тихо исправляет всё, что находит. Это кажется эффективным, но при этом удаляется информация, которая вам была нужна: вы больше не можете отличить правильную работу от неправильной, которая была исправлена без вашего ведома.
Поэтому делайте рецензента только с правом чтения. Он формирует критику, а действия на основе этой критики представляют собой ещё один этап с собственными следами. Разделение этих процессов позволяет сохранить сигнал, который можно прочитать.
Второй приём — пробный запуск. Для любых необратимых действий инструмент принимает флаг, по умолчанию описывает, что он собирался сделать, и действует только тогда, когда кто-то намеренно отключает этот флаг. Безвредное поведение является стандартным, поэтому оплошность ничего не стоит. В демонстрации инструмент исправления сообщает, что собирается переписать файл total.py, ничего не записывает, и ошибка «off-by-one» остаётся в конце.
Один execute включает в себя обе правила:
def execute(call):
if call.name == "review":
snapshot = dict(CODE) # a copy, so the reviewer cannot write
src = snapshot[call.args["path"]]
return f"VERDICT: {'off-by-one' if '+ 1' in src else 'looks good'}"
if call.name == "apply_fix":
if call.args.get("dry_run", True): # on by default, turned off on purpose
return f"DRY RUN: would rewrite {call.args['path']}, nothing written"
CODE[call.args["path"]] = call.args["new"]
return f"wrote {call.args['path']}"
Рецензент замечает ошибку типа «один слишком много» и не имеет возможности её исправить.
Слой 5: конвейеры контекста
Что попадает в модель и за что взимается плата?
Конвейер контекста определяет, как информация попадает в окно модели и сколько из неё принимается.
Именно здесь происходит оплата. За каждый токен, который попадает в модель, взимается плата в момент его ввода и снова при каждом последующем вводе, поэтому именно здесь возникает сорокакратная разница в стоимости, а не на Слое 1.
В примитивной настройке загружается всё: полный файл с вопросом об одной функции, полные результаты поиска для однострочной задачи и вся история. Это работает, но к двадцатому ходу вы снова платите за то, что уже было прочитано на ходах с одинного по девятнадцатый.
Решение заключается в делегировании задач и их обработке путем фильтрации. Подагент работает в отдельном окне, просматривает десятки тысяч токенов и передает обратно один-два тысячи. Основной поток пропускает необработанные результаты поиска и получает только ответ.
В коде всё сводится к тому, какой счётчик увеличивается:
def subagent_search(query):
"""Its own window. The main thread never pays for this reading."""
global subagent_tokens
subagent_tokens += sum(len(v.split()) for v in CORPUS.values())
return next(f"{n}: {b.split('ANSWER:')[1].strip()}"
for n, b in CORPUS.items() if "ANSWER:" in b)
def execute(call):
global main_tokens
distilled = subagent_search(call.args["q"])
main_tokens += len(distilled.split()) # the only line that bills you
return distilled
Демонстрация преднамеренно масштабирована до небольших размеров и проводится честно. Вопрос и ответ одинаковы в обоих случаях, однако основной поток обрабатывает 9 602 токена в одном случае и 2 токена в другом.
Это также объясняет, почему цепочки подагентов разочаровывают пользователей. Когда каждый шаг ждет предыдущего, возникает изоляция контекста, и полного параллельного ускорения не происходит. Действительно независимые шаги должны выполняться одновременно. В противном случае приходится смириться с тем, что именно изоляция является ценой. Часто это оправдывает стоимость, если вы знаете, что включено в счет.
Чего пока никто не может сказать
Доверие общественности к этой теме превзошло наличие доказательств, поэтому важны все оговорки.
- Ничто здесь не прошло рецензирование коллегами. По состоянию на август 2026 года каждое исследование, посвящённое этому устройству, представляет собой предварительную публикацию на arXiv без рецензирования. Самый тщательно спланированный эксперимент описывается как начальная работа, находящаяся на рассмотрении на семинаре, а наиболее известная статья-основа называет себя позиционным документом без собственных экспериментов. Считайте всё это, включая данный текст, нерецензированным и повторяйте сравнение в своих задачах перед тем, как планировать бюджеты на его основе.
Сопутствующие скрипты
К этой идее прилагаются десять коротких скриптов. Каждый слой работает с соответствующим защитным механизмом, а тот же агент — без него. Они запускаются офлайн без API-ключа, поскольку скриптовый заместитель заменяет модель и выполняет фиксированный набор вызовов инструментов. Проверка разницы в результатах работает лучше, чем полагание на надежность.
git clone https://github.com/paoloap-py/agent-harness-guide
cd agent-harness-guide
python3 run_all.py # all five layers, guard off then on, side by side
python3 test_harness.py # asserts every difference above, 10 checks
В документе README пять уровней представлены в виде чек-листа для аудита с одним повторяющимся вопросом: обеспечивает ли код соблюдение правил или же это просто фраза в запросе? Любая галочка, которую невозможно поставить, указав путь к файлу, означает правило, на соблюдение которого можно только надеяться.
Как это работает на практике
Хорошее правило, размещённое не в подходящем месте, практически бесполезно. В описанном выше случае это привело к пятнадцати нарушениям, одиннадцати из которых произошли за один день, и к дню, который никто не хочет вспоминать. Именно перемещение правила позволило ему сработать, причём ни одно слово в нём не было изменено.
В этом и заключается суть. Модель — это то, что вы покупаете. Всё остальное — это то, что вы создаёте, и именно это определяет стоимость агента и частоту выполнения им действий, которые вы никогда не одобряли. Оба аспекта относятся к инженерии, но только второй находится в вашем контроле.
Часто задаваемые вопросы
Что именно представляет собой харнес для агента? Всё, что окружает модель: цикл обработки запросов, проверки перед выполнением, сандбокс, сохранение данных между сессиями, система рецензирования и конвейер, определяющий содержимое каждого этапа обработки. Цикл состоит из шести строк, или двадцати с обработкой ошибок, а всё остальное относится к харнесу.
Является ли харнес важнее модели? С точки зрения затрат — да, поскольку количество используемых токенов на решённую задачу может варьироваться в 40 раз. Что касается успешности решения задач — нет. Различия в проценте успешного выполнения задач с использованием разных харнесов составляют от 0 до 8 пунктов, причём бывают периоды, когда этот показатель становится отрицательным; авторы Terminal-Bench приходят к выводу, что для производительности обычно важнее выбор модели.
Где ремни безопасности стоят дороже всего? В контексте. Каждый токен, показываемый модели на каждом шаге, оплачивается на каждом шаге. Поэтому уровень 5 является самым дорогим, и это объясняет, почему передача поиска подагенту меняет сумму расчета, не меняя при этом ответа.
Считаются ли правила разрешений частью механизмов безопасности?
Нет. Проверка ремней безопасности учитывает только те вызовы, которые агент фактически совершил, в то время как команда оболочки, символическая ссылка или путь .. могут обратиться к тому же файлу без проверки. Настоящее ограничение исходит от операционной системы: процесс запускается с минимальными правами доступа, а то, что необходимо для выполнения задачи, добавляется позже.
Создавать собственные ремни безопасности или использовать готовые? Лучше использовать готовые и оставаться с ними. Переход на другие ремни безопасности стирает накопленные знания из файлов конфигурации, и каждое правило, добавленное после реальной ошибки, приходится формировать заново.
Связанные материалы
- Агенты, совершенствующиеся благодаря навыкам: повторное использование, отбор, ремонт и проверка — четырехэтапная концепция для агентов, способных улучшать собственные навыки: сохранение рабочего кода, усовершенствование описаний, предложение проверенных исправлений и проверка результата перед передачей задачи.
- Инструментарий для производственного использования Claude Code: шесть уровней от Slack до объединенного кода — как шестилеточный инструментарий преобразует задачи из Slack в объединенный код: прием событий, циклы работы агентов с контролем доступа, сохранение состояния, самовосстановление, возможности мониторинга и управление оператором.