П’ять шарів агента-харнесу з кодом, який не працює без них.
П’ять шарів механізму керування агентом, кожен з кодом, який не може функціонувати без нього: межа виконання, сандбоксування, пам’ять, цикли перевірки та конвеєр контексту.
Джерела: `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. Ця функція визначає, що може робити модель, до чого вона може отримати доступ, що вона зберігає, хто її перевіряє та що опиняється перед нею.
Разом ці рішення становлять основу для роботи системи, і багато коментарів стверджують, що вони мають більше значення, ніж вибір самої моделі. Дані підтримують більш обмежену тезу, яка, до речі, є більш практичною.
Кріплення має перевагу за ціною. Попередній варіант дослідження від червня 2026 року порівняв три кріплення (Goose, OpenCode, OpenHands-SDK) на двох моделях, використовуючи стратифікований набір з 50 завдань Terminal-Bench Pro — складнішу версію набору агентів Terminal-Bench. Кількість токенів, використаних на одне вирішене завдання, відрізнялася між кріпленнями у 40 разів. Перехід на кращу модель змінював цей показник лише у 1,0–1,3 рази.
Воно не має переваги за функціональністю. Рівні успішності виконання завдань відрізнялися між кріпленнями на 0–8 пунктів, і з урахуванням інтервалів довіри bootstrap усі значення, окрім найбільшої різниці, знаходилися близько нуля. Самі автори називають результати точності описовими, враховуючи кількість експериментів 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, нічого не записує, і проблема залишається до кінця.
Одна команда 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']}"
Рецензент помічає помилку типу «off-by-one», але не має можливості її виправити.
Рівень 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 роблять висновок, що для продуктивності зазвичай важливішим є вибір моделі.
Де ремень безпеки коштує найбільше? У контексті. Кожен токен, який показується моделі на кожному кроці, оплачується на кожному кроці. Це робить Layer 5 найдорожчим та пояснює, чому передача пошуку підагенту змінює суму рахунку, не змінюючи відповіді.
Чи вважаються правила дозволів частиною безпеки?
Ні. Перевірка ременя безпеки бачить лише той виклик, який агент фактично здійснив, тоді як команда оболонки, символічний посилання чи шлях .. можуть отримати доступ до того самого файлу без перевірки. Справжня межа полягає в операційній системі: запустіть процес із обмеженим доступом та додайте те, що потрібно для виконання завдання.
Створити власний ремень безпеки чи скористатися готовим? Краще скористатися готовим та залишатися з ним. Перехід на інший стирає накопичені знання з файлів конфігурації, і кожне правило, написане після справжньої невдачі, доводиться створювати заново.
Пов’язана література
- Агенти, які самовдосконалюються завдяки навичкам: повторне використання, відбір, виправлення та перевірка — чотириетапний підхід до створення агентів, які покращують власні навички: зберігання функціонального коду, удосконалення описів, пропозиції перевірених виправлень та попередня перевірка результатів перед передачею.
- Інструментарій для робочого використання Claude Code: шість рівнів від Slack до об’єднаного коду — як інструментарій з шістьма рівнями перетворює заявки з Slack на об’єднаний код: прийом подій, цикли роботи агента, стабільний стан, самовідновлення, можливість спостереження та керування з боку оператора.