applied-ai2026-08-1319 минNikolay Angelov

Harness Engineering

Моделът връща текст. Всичко останало — какво влиза, дали действието ще се случи, кой го спира и как агентът разбира, че е успял — е код, който някой е написал.

Harness — системата около модела

В последната статия ставаше дума за Context Engineering. Тя обаче подмина един въпрос, който стои под всичко останало: кой решава какво влиза в контекста?

Това не е моделът. Моделът получава прозорец и връща текст. Всичко останало — какво е влязло, дали изходът му ще има някакъв ефект върху света, дали ще бъде извикан отново и кога ще спре — се решава от кода около него.

Този код стана известен като harness, и проектирането му е отделна дисциплина.

Продължаваме със същия пример: агент, който мигрира двеста файла от една библиотека към друга. Context engineering питаше какво вижда агентът на файл 60. Harness engineering пита друго:

  • Откъде агентът знае, че файл 59 наистина работи?
  • Какво се случва, когато файл 61 счупи билда?
  • Кой го спира, ако започне да трие?
  • Кой продължава от файл 140, ако процесът умре?
  • Кой решава кое е вярното, ако два подагента се върнат с различни отговори?

Нито един от тези въпроси не е за модела.


Моделът, контекстът и harness-ът

Едно сравнение от работното място подрежда трите неща наведнъж.

Моделът е способността. Компанията ви наема високоинтелигентен и образован служител — този ценз не се променя от местоработата на този човек.

Контекстът е онбордингът. Първите две седмици му се дава да чете документация, обясняват се процесите, показват му се проблеми в дизайна и защо някой процес или модул изглежда странно. Това отново не го прави по-умен — прави го информиран за служебния контекст.

Harness-ът е всичко останало в компанията. Лаптопът, достъпът до софтуер, правата в production, CI-ят, код ревюто, това че някой одобрява деплоя, дефиницията на „готово“, ретроспективата.

Третото не е пасивна инфраструктура, която служителят ползва, когато му потрябва. То определя какво изобщо може да се случи.

Harness-ът решава кога моделът се извиква. Решава какво вижда — значи цялото context engineering се случва вътре в harness-а, не до него. Решава дали предложеното действие ще бъде изпълнено, или ще бъде спряно за одобрение. Решава кога работата е приключила. Моделът не може да откаже нито едно от тези решения, защото изобщо не знае, че се вземат.

Не моделът ползва harness-а. Harness-ът ползва модела.

Оттук идва изводът, който прави дисциплината смислена:

Блестящ нов служител без лаптоп, без достъп до кода, без начин да пусне тестове и без никой, който да му каже дали работата му върши работа, не произвежда нищо. Никой не би казал „трябва ни по-умен служител“. Всички биха казали, че организацията е счупена.

При агентите казваме „трябва ни по-добър модел“ изненадващо често.

Най-подценяваното в целия този списък е възприятието — дали агентът изобщо вижда резултата от действието си. Агент, който действа, но не вижда какво е станало, е един от най-честите провали в цялата дисциплина, и затова ще се говори по-подробно след малко.

Модел, контекст и harness — способност, знание и системата около тях


Какво е harness

Harness-ът е runtime слоят около модела: цикълът, инструментите, състоянието, обратната връзка, гейтовете, възстановяването, прекратяването, наблюдаемостта и оркестрацията, когато агентите станат повече от един.

Най-краткото операционно определение обаче е: harness-ът е цикълът, който превръща извикването на модел в работещ процес.

messages = [{"role": "user", "content": task}]

while True:
    response = client.messages.create(
        model="claude-opus-5",
        max_tokens=16000,
        tools=tools,
        messages=messages,
    )

    if response.stop_reason == "end_turn":
        break

    messages.append({"role": "assistant", "content": response.content})

    results = []
    for block in response.content:
        if block.type == "tool_use":
            results.append({
                "type": "tool_result",
                "tool_use_id": block.id,
                "content": execute(block.name, block.input),
            })

    messages.append({"role": "user", "content": results})

Петнадесет реда. Това е работещ агент — и е целият harness в най-примитивната си форма.

Заслужава внимание какво не е в тези петнадесет реда, а трябва да бъде в продукция:

  • execute() изпълнява всичко, което моделът поиска, без да пита никого;
  • никъде не се проверява дали действието е успяло — резултатът се подава наивно;
  • при грешка целият цикъл гърми и часовете работа изчезват;
  • няма условие за спиране освен добрата воля на модела;
  • ако процесът умре на файл 140, всичко започва от нула;
  • никой отвън не вижда какво се е случило вътре.

Harness engineering е попълването на този списък. Всяка следваща глава е един от редовете му.

Няма нужда цикълът да се пише на ръка — SDK-тата предлагат готов tool runner, а има и платформи, които го хостват. Но решенията остават на този, който строи системата. Готовият цикъл спестява while-а, не архитектурата.


Три вида провали

Това е най-практичната част от статията, защото много загубено време в агентни системи отива в лекуване на грешния слой.

Слой Провалът е Симптом
Модел разсъждава зле при добра информация и работещи инструменти логиката е грешна, но действията са изпълнени и видени
Контекст разсъждава добре, но върху грешна, остаряла или отровена информация уверено прави нещо, което е било вярно преди тридесет стъпки
Harness не може да действа, не вижда резултата, не е спрян навреме или работата му се губи твърди, че е готово; повтаря същото действие; спира по средата

Диагностиката по симптом:

Наблюдаваш Най-вероятно е
Агентът казва „готово“, а нищо не работи harness — няма обратна връзка
Повтаря същото неуспешно действие harness — грешката не стига до него
Изтрива или деплойва нещо, което не е трябвало harness — липсва гейт
Спира по средата и обявява успех harness — няма дефиниция за „готово“
Работата изчезва при рестарт harness — състоянието е живяло в прозореца
Избира грешния от петдесет инструмента контекст — объркване
Ползва API, което не съществува контекст — отравяне… или harness (вж. по-долу)
Прави глупост с пълна и вярна информация модел

Предпоследният ред от таблицата заслужава внимание, защото свързва двете статии.

В статията за Context Engineering отравянето беше описано като контекстен провал: агентът измисля сигнатура на файл 12 и я преизползва до файл 90. Вярно е — но е и harness провал. Ако в цикъла има type checker, измислената сигнатура умира на файл 12 и никога не влиза в историята.

Оттук следва принцип, който държи изненадващо широко:

Добрата обратна връзка намалява нуждата от context engineering.

Агент, който може да провери, не носи предположения напред. Голяма част от натрупването, срещу което се борим с компактиране и изчистване, съществува само защото агентът е трябвало да помни неща, вместо да може да ги провери.

Три вида провали — модел, контекст, harness


Патърните

1. Обратната връзка — най-важното нещо.

Агент без обратна връзка не работи. Той генерира. Разликата е, че генерирането изглежда еднакво успешно и когато е вярно, и когато не е.

Ако агентът напише код и никога не го пусне, той ще ти докладва успех. Не защото лъже, а защото няма как да знае друго. Изходът му е текст, който изглежда като работещ код, и това е цялата информация, с която разполага.

Затова първият въпрос към всеки агент не е „какви инструменти има“, а „как разбира, че е успял?“

Нивата, в нарастващ ред на стойност:

Ниво Инструмент Хваща
Никакво нищо; агентът винаги „успява“
Синтактично linter, парсер счупен код
Типово type checker, компилатор несъществуващи API, грешни сигнатури
Поведенческо тестове грешна логика
Семантично човек или втори агент правилно изпълнена грешна задача

За миграцията: агент с тестове в цикъла работи. Агент без тестове произвежда двеста файла, които изглеждат мигрирани.

Ключовото при имплементацията е, че грешката трябва да се върне като резултат, не да се хвърли като изключение:

def execute(name, args):
    if name == "migrate_file":
        write_file(args["path"], args["content"])
        result = run_tests(args["path"])          # ← harness-ът проверява
        if result.failed:
            return {
                "content": f"Тестовете паднаха:\n{result.output}",
                "is_error": True,                 # ← и връща провала обратно
            }
        return {"content": "OK, тестовете минават"}

Хвърлена грешка прекъсва нормалния agent loop, освен ако harness-ът не я прихване и не я превърне в tool result. Върната грешка като структуриран резултат е информация, върху която моделът може да действа — вижда какво е паднало и коригира.

Правило, което си струва да се запомни: на агента се дава същата обратна връзка, която би получил и човек. Никой не онбордва разработчик, като му забранява да пуска тестовете.

2. Дизайн на инструментите — какво може harness-ът да прихване

Тук се крие най-подценяваното решение в цялата дисциплина.

Моделът не знае къде минава границата на сигурност, каква е политиката за одобрения, нито как изглежда интерфейсът. Той емитира заявки за действия. Формата на тези заявки определя какво harness-ът изобщо може да направи с тях.

bash дава максимална широта. Агентът може почти всичко. Но на harness-а му остава непрозрачен низ — една и съща форма за всяко действие:

{ "name": "bash", "input": { "command": "rm -rf ./generated && npm run build" } }

Специализираният инструмент дава типизирани аргументи, върху които може да се вземе решение:

{ "name": "delete_path", "input": { "path": "./generated", "recursive": true } }

Втората форма може да бъде спряна за одобрение, логната, показана в интерфейс, засечена за паралелна безопасност. Първата също може да бъде контролирана, но вече изисква анализ на shell командата — много по-трудна и ненадеждна граница.

Кога си струва действието да получи собствен инструмент:

  • Граница на сигурност. Нещо, което трябва да минава през одобрение.
  • Проверка за остарялост. Специализиран edit може да откаже запис, ако файлът е променен, откакто агентът го е чел последно. При общ bash подобно ограничение е значително по-трудно да се наложи структурно — и си струва да се забележи, че така harness-ът предотвратява контекстен провал.
  • Визуализация. Действия, които трябва да се покажат на човек по определен начин.
  • Паралелизъм. Harness-ът може да пуска едновременно неща, за които знае, че са безопасни. Ако всичко е обвито в общи shell команди, класифицирането на тези действия става много по-трудно.

Практическото правило: започва се с bash за широта, а в отделен инструмент се повишава онова, което трябва да се гейтва, показва, одитира или паралелизира.

3. Гейтове — критерият е обратимостта

Изкушението е да се гейтва по „опасност“. По-полезната ос е друга.

Колко струва да се върне назад?

Евтино за връщане — пуска се. Скъпо или невъзможно — гейтва се, независимо колко безобидно изглежда.

Действие Обратимо? Гейт
Редакция на файл в git да, лесно не
Пускане на тестове да, нищо не се променя не
rm -rf извън работната директория не да
Изпращане на имейл не да
Деплой в продукция понякога, скъпо да
Заявка към платен API не — парите са похарчени зависи от сумата

Забелязва се, че „изпращане на имейл“ може да е по-опасно от изтриването на git-tracked файл, въпреки че звучи по-безобидно. Файлът може да се възстанови. Имейлът вече е тръгнал.

Двата практични детайла: гейтът трябва да е в harness-а, не в промпта — инструкция „винаги питай преди да триеш“ е препоръка, а не ограничение. И отказът трябва да носи причина, за да може агентът да опита друго, вместо да блъска в същата стена.

4. Възстановяване — състоянието не бива да живее в прозореца

Ако процесът умре на файл 140 и работата започне от нула, harness-ът е счупен.

Тук двете статии се срещат директно. Патърнът „пиши навън“ от context engineering не е само за икономия на токени — той е условието за възстановимост. Прогрес файлът оцелява след рестарт. Контекстният прозорец — не.

Минимумът е три неща да бъдат извън прозореца: докъде е стигнала работата, какво е научено по пътя и кое е следващото. Ако това е в устойчиво хранилище, рестартът струва един прочит.

Свързан въпрос е този за повторните опити. Разликата, която има значение:

  • Преходна грешка — мрежа, rate limit, timeout — harness-ът може да повтори сам, без да безпокои модела. Нищо не е нужно да влиза в контекста.
  • Смислена грешка — тестът падна, файлът не съществува — отива при модела, защото е информация, върху която трябва да се разсъждава.

Смесването на двете е чест дефект: преходните грешки влизат в прозореца като съдържание и го тровят, а смислените се повтарят механично и никога не се коригират.

5. Прекратяване — агентите не знаят кога да спрат

Три различни провала се крият под една дума.

Спира твърде рано. Обявява успех на файл 60. Лекува се с проверимо условие за „готово“: не „когато моделът каже“, а „когато 200 от 200 файла имат минаващи тестове“. Дефиницията за готово принадлежи на harness-а.

Не спира изобщо. Върти един и същ цикъл. Harness-ът може да засича повторения — три пъти едно и също действие с един и същ резултат е сигнал за прекъсване, а не за четвърти опит.

Свършва ресурсът по средата. max_tokens е ограничение върху конкретния model response. Но задачата има и друг бюджет: време, токени, стъпки, пари или брой tool calls. Този бюджет принадлежи на harness-а.

Например:

MAX_STEPS = 200
MAX_COST = 10.00

while not done:
    if steps >= MAX_STEPS or cost >= MAX_COST:
        checkpoint_state()
        break

    response = call_model(...)
    cost += estimate_cost(response)
    steps += 1

Разликата е важна. Ако се разчита само на лимита на един response, моделът може да бъде отрязан по средата на работа. Ако harness-ът следи бюджета на цялата задача, той може да спре контролирано, да запише прогреса и да остави процеса във възстановимо състояние.

6. Наблюдаемост — не се оправя това, което не се вижда

Агентът е недетерминиран процес, който може да вземе стотици решения. Когато сгреши, единственият начин да разбереш защо е да видиш какво е видял и какво се е случило след това.

Минимумът за всяка стъпка: какво е влязло в контекста, какво действие е предложил моделът, какво е върнал инструментът, колко време и ресурси е струвало. Без това дебъгването е гадаене, а „работи по-добре днес“ — не е инженерство.

Полезните метрики зависят от системата, но няколко почти винаги си струват: колко стъпки отнема една задача, колко tool calls се провалят, колко retry има, колко често се стига до гейт, каква е цената на успешна задача и как се използва контекстният кеш.

Първите хващат цикли и излишна работа. Последното може да покаже тихи промени в контекста, които унищожават cache hit rate и качват цената, без поведението видимо да се е променило.

Анатомия на harness-а — контекст, модел, инструменти, гейтове, изпълнение, обратна връзка

7. Оркестрация — когато агентите станат повече от един

Ако задачата се разпада на независими части, всяка може да получи свой прозорец. В предишната статия това беше „Изолирай“ — един от четирите глагола, разгледан като начин да се пести контекст. Тук е другата му страна: изолацията е контекстна печалба, но оркестрацията е harness работа. Кой поражда подагент, какво вижда той, какво връща и кой решава какво значи всичко това — нищо от това не се случва в модела.

За миграцията: подагент на файл или на група файлове. Всеки получава своя файл, правилата от прогрес файла и нищо друго. Родителят никога не вижда съдържанието на двестате файла — вижда двеста реда резултат.

Това е патърнът, който може да умножи капацитета, вместо просто да го разтяга. Останалите шест го пестят. И е единственият, при който harness-ът може да сгреши по начин, който при един агент просто не съществува.

Четирите решения

Какво вижда подагентът. Той тръгва от нула. Каквото му трябва, трябва да му се подаде — това е брифингът и е най-честият провал. Недобрифиран подагент преоткрива всичко сам и струва повече, отколкото спестява.

Какво може да прави. Правата се раздават на подагент, не глобално. Агент, който само чете и обобщава, няма нужда от право на запис. Тук раздел 3 се прилага повторно, но на друго ниво.

Какво връща. Ако петима подагенти върнат свободен текст, родителят трябва да прочете пет есета — преместил си контекстния проблем, не си го решил. Договорът за връщане е конкретен harness лост:

RESULT_SCHEMA = {
    "type": "object",
    "properties": {
        "file":       {"type": "string"},
        "status":     {"enum": ["ok", "failed", "skipped"]},
        "tests_pass": {"type": "boolean"},
        "new_rule":   {"type": ["string", "null"]},   # правило, ако е научено
        "blocked_by": {"type": ["string", "null"]},
    },
    "required": ["file", "status", "tests_pass"],
}

Пет полета вместо пет параграфа. Родителят може да ги обработи, без да ги чете.

Какво става при провал. Един от петимата умира. Пада ли всичко? Повтаря ли се? Продължава ли с четирима и отбелязва ли липсващия? Това решение трябва да е взето предварително — по подразбиране обикновено е „тихо изчезва“.

Проблемът със сливането

Ето въпроса, който няма аналог при един агент.

Два подагента мигрират различни файлове. И двата срещат .timeout(ms). Първият работи по файл, в който таймаутът само ограничава заявката — заключава, че еквивалентът е AbortSignal.timeout(ms). Вторият работи по файл, в който таймаутът трябва да може да се отменя отвън — заключава, че е нужен ръчен AbortController.

И двамата са прави. За своя файл.

Родителят получава две правила, които си противоречат, и записва едното в прогрес файла. Оттам нататък сто и петдесет файла се мигрират по правило, вярно за половината от тях.

Забелязва се, че никой подагент не е халюцинирал, никой не е сгрешил и никакъв контекст не е бил отровен. Дефектът е изцяло в сливането — тоест в harness-а.

Какво може да направи родителят:

Стратегия Кога Цена
Взима последното никога — това е позиция, не преценка тиха грешка
Избира по критерий когато критерият е дефиниран предварително трябва да го измислиш
Пита трети агент когато има с какво да се разсъди пари, и може да не реши
Ескалира към човек при необратими или скъпи решения латентност
Задържа и двете когато правилото има обхват усложнява прогрес файла

Последният ред е верният отговор в този пример: правилото не е едно, а зависи от това дали таймаутът трябва да е отменяем. Но за да стигне дотам, harness-ът първо трябва да е забелязал противоречието.

Оттук и принципът:

Противоречието между подагенти е сигнал, не шум.

Ако двама агенти с еднакви инструкции стигат до различни изводи, това е информация: задачата може да е двусмислена, контекстът — недостатъчен, или решението — действително да има повече от един валиден вариант. Harness, който тихо избира един от двата отговора, изхвърля най-ценното нещо, което току-що е произвел.

Два агента, два локално правилни резултата, един конфликт и слой за разрешаване

Кога да не го правиш

Подагентът плаща, за да установи контекст, който родителят вече има. Ако задачата се свършва с три извиквания, делегирането губи.

Цената е реална: координация, латентност, загуба на кеша при друг модел — и най-подценяваното, че отстрани изглежда като напредък. Пет паралелни агента изглеждат по-продуктивни от един. Не са, ако четири от тях преоткриват едно и също.

Правило: делегира се, когато частите са наистина независими и всяка е по-голяма от брифинга си.


Защо harness-ът е продуктът

Тук е интересната индустриална част.

Коденето с агенти се превърна в конкурентен пазар за много кратко време, а продуктите често имат достъп до модели със сходни способности. Разликата в резултата обаче може да е огромна — и голяма част от нея идва от harness-а.

Кой инструмент е повишен от bash в отделен. Какво се показва на човека и какво се изпълнява мълчаливо. Кога се пита за одобрение. Какво се случва при паднал тест. Как се възстановява прекъсната сесия. Как се управлява контекстът между стъпките.

Това е и причината един и същ модел да върши работа в едната среда и да разочарова в другата. Когато моделът е един и същ, първото място за търсене на разликата често е системата около него.

Няколко неща от екосистемата, които си струва да се знаят по име:

Готови agent loops. Не е нужно while-ът винаги да се пише на ръка. SDK-та и agent frameworks могат да въртят цикъла над вече дефинираните инструменти и да оставят точки за намеса — за одобрение, логване, промяна на резултата или прекратяване. Има и хоствани варианти, при които част от цикъла и execution средата са чужда грижа. Изборът е между това колко от harness-а се пише на ръка и колко от него остава под контрол.

MCP е част от инструменталната повърхност. Той стандартизира начина, по който capabilities като tools и resources могат да бъдат изложени към AI приложения. Това премахва част от интеграционната работа, но не решава orchestration-а: кои действия се гейтват, какви права има агентът, как се правят retry, recovery и termination — това остава работа на системата около модела.

A2A е протоколна граница за делегиране. Когато една част от задачата се предава на друг агент, вече не е нужно да се споделя неговият вътрешен цикъл, инструменти и контекст. Виждат се договорената задача, статусът и резултатът, които минават през протокола. Това прави границата между две агентни системи експлицитна: едната делегира работа, другата сама решава как да я изпълни.

Evals са обратната връзка за самия harness. Обратната връзка в цикъла казва на агента дали е успял. Evals казват на инженера дали промяната в harness-а е подобрила нещо. Без тях всяка настройка е вкус.


Какво да се има предвид

Първият въпрос е откъде агентът знае, че е успял. Ако отговорът е „не знае“, всичко останало е второстепенно. Оттам се започва.

Проверява се какво се случва при провал. Пуска се агент срещу задача, която ще падне. Ако грешката не стига до него в използваема форма, това е една от най-евтините възможни поправки.

Прави се списък на необратимите действия. После се проверява кои от тях минават през гейт. Разликата между двата списъка е рискът, който системата носи в момента.

Процесът се убива по средата. Буквално. Ако рестартът не може да продължи, състоянието живее на грешното място.

Преглежда се един пълен trace. Не обобщение — целият. Повечето хора не са гледали какво точно е видял агентът им на стъпка четиридесет, а точно там често е отговорът.

Преброяват се колко от инструментите са bash. После — за колко от тях би било добре да може да се каже „не“.

Ако има подагенти, се проверява какво прави родителят при противоречие. Ако отговорът е „взима последния прочетен“, това е решение, което никой не е взел съзнателно.


Финални думи

Prompt engineering питаше какво да се каже на модела.

Context engineering пита какво вижда моделът.

Harness engineering пита какво може моделът да направи, как разбира дали е станало и кой го спира.

Трите не се конкурират — те са три слоя на една и съща система. И много от провалите в продукция се оказват в най-долния, защото той е слоят, върху който контролът е най-пряк.

Моделът е даден. Контекстът е дисциплина. Harness-ът е код, който някой трябва да напише.

Изкушаващо е да се мисли за модела като за нещото, което ползва инструменти. По-полезно е обратното:

Harness-ът е системата. Моделът е компонент в нея — най-способният, но не той решава.

Работата не е моделът да стане по-умен. Тя е да се построи организацията, в която един умен служител може да свърши нещо полезно, без да счупи нищо необратимо, и в която някой ще забележи, ако сгреши.