Engineering the Agentic Stack · Deel 6

Harness Engineering voor AI agents: control loops ontwerpen

Automatische vertaling Dit artikel is automatisch vertaald vanuit de oorspronkelijke Engelse versie.

De reasoning loop van een agent kiest de volgende actie. De harness levert context, valideert voorgestelde tool calls, autoriseert ze, dispatcht wat wordt geaccepteerd, legt de resultaten vast en beslist of de taak voltooid is.

Voor de eerste drie van die taken bestaat al een artikel. Memory levert de context, tool use bepaalt wat kan worden voorgesteld en security beslist wat wordt uitgevoerd. Ze verschenen als afzonderlijke posts omdat het afzonderlijke engineeringproblemen zijn. De keuze tussen Qdrant en pgvector heeft niets te maken met het schrijven van een PreToolUse deny rule.

Ze hebben ook een gemeenschappelijk moment: de kloof tussen het moment waarop het model een actie benoemt en het moment waarop de machine die uitvoert. Elk onderdeel beantwoordt een vraag over die kloof, en de harness is de code die de kloof lang genoeg openhoudt om alle drie de vragen te stellen.

Voor engineers die coding-agent harnesses bouwen of beoordelen, is de resterende taak bepalen of afgerond werk daadwerkelijk compleet is en aantonen dat elke control in de loop de kosten ervan rechtvaardigt. Part 5 behandelde de runtime die het proces op de achtergrond actief houdt.

Na de eerste expliciete acceptance check is elke extra retry, handoff of evaluator een hypothese over een waargenomen failure. Het verdient alleen een plaats wanneer een gecontroleerde vergelijking laat zien dat het helpt.

De eenvoudigste acceptance check is gemakkelijk te schrijven voor een kleine research agent zoals die waarmee deze serie is begonnen — een LangGraph agent die marktdata ophaalt en een analyst report schrijft. Een hook buiten het model valideert het rapport tegen een schema en controleert of het daadwerkelijk stock tickers bevat; een malformed report houdt de run open. Twaalf regels gewone code zijn voldoende, en het model mag zijn eigen output niet als well-formed verklaren. De eigen gate van de repo is zachter: een fresh-context evaluator die stemt, gevolgd door een human. (Part 4 schetst de deterministische variant.)

Wat dit voorbeeld niet laat zien, is het interessante deel: wat er gebeurt wanneer de evidence ambigu is, wanneer een retry iemand dubbel kan belasten of wanneer het werk langer duurt dan de session waarmee het begon. Daarvoor is een taak nodig met een scherpere pass/fail-grens dan een research report heeft. De research agent blijft het voorbeeld voor de acceptance check; een kleine fictieve store repository wordt toegevoegd voor de gevallen met retry en handoff. De coding task bestaat uit het verlagen van de threshold voor een automatische korting van 10% van $100 naar $75 in src/checkout.py. De repository heeft twee verplichte checks:

  • pytest tests/test_checkout.py verifieert de discount calculation.
  • pnpm playwright test tests/checkout_discount.spec.ts voegt een item van $80 toe aan een lokale test store en controleert of de checkout-pagina een korting van $8 toont.

Het voorbeeld is een teaching fixture, geen echte applicatie of benchmark. Elke poging begint vanaf dezelfde commit en met dezelfde seeded test data. De harness mag de wijziging alleen accepteren wanneer beide commands slagen en de trace die resultaten koppelt aan de geteste commit.

Het diagram volgt de discount change van proposal naar evidence. De harness levert de task en bestanden, controleert de voorgestelde argumenten en permissions van edit_file en dispatcht de geaccepteerde call. Nadat de runtime de edit heeft toegepast, voert de harness de genoemde unit- en browser acceptance tests uit. Een failed command gaat als evidence terug naar het model voor een volgende turn; twee geslaagde commands maken de wijziging eligible for acceptance.

Een discount change door de harness control loopEen discount change door de harness control loop


Wat de harness beheert

OpenAI’s Codex loop walkthrough beschrijft de basiscyclus. De harness assembleert een prompt, vraagt het model om de volgende actie, stuurt een geaccepteerde tool call naar de runtime en voegt het resultaat toe. Daarna vraagt de harness het opnieuw. Dit wordt herhaald totdat de harness het resultaat accepteert of de control teruggeeft aan de user.

Implementations kunnen meerdere verantwoordelijkheden in één proces samenvoegen. De failure boundaries blijven verschillend:

TermTaakCoding-agentvoorbeeld
ModelStelt tekst, een tool call of een final answer voorStelt een edit voor in src/checkout.py
Reasoning loopKiest de volgende stap uit de beschikbare contextInspect, edit, test, opnieuw inspecteren
HarnessLevert context, valideert proposals, autoriseert ze, dispatcht geaccepteerde calls, legt resultaten vast en controleert completionStaat edits onder src/ toe en vereist beide genoemde tests
RuntimeVoert geaccepteerde calls uit en houdt state buiten het worker process actiefSession log, sandbox, checkpoint store, trace backend

De runtime-row omvat vier dingen: session, sandbox, checkpoint en trace. Alle vier slaan state op of beperken de execution. Het model stelt de actie voor en de reasoning loop kiest de volgende stap. De harness beslist of een voorgestelde call mag worden uitgevoerd en of de evidence voldoende is om te stoppen. Daarom krijgt de harness een eigen artikel. Part 5 telt de harness samen met die vier als een van de vijf primitives die je vóór shipping moet plaatsen; dit artikel splitst hem opnieuw uit.

Wanneer er een failure optreedt, diagnoseer je de boundary die moet reageren. Een slecht plan kan betere instructies of betere model reasoning vereisen. Als edit_file een path buiten src/ target, moet de harness dit afwijzen. Een sandbox process dat sterft voordat de edit draait, hoort bij de runtime; die moet de worker herstarten of de crash rapporteren.

Waar de eerdere delen terechtkomen

De harness-row hierboven doet het meeste werk in die tabel en is waar Parts 2, 3 en 4 eindigen. Elk deel beslist één ding over een afzonderlijke turn:

Eerder deelWat het voor deze turn beslistWaar het in de walkthrough van de volgende sectie werkt
Part 2 — memoryWelke eerdere state in de prompt terechtkomtStep 1, de context builder
Part 3 — tool useWelke actions bestaan en hoe een gevalideerd resultaat eruitzietArgumentvalidatie in step 3 en de result shape in step 4
Part 4 — securityOf deze specifieke call nu mag worden uitgevoerdStep 3, de path check en de approval decision
Part 6 — dit artikelOf de resulterende evidence de run beëindigtSteps 5 tot en met 7, de acceptance checks en trace

Waar elk deel van de serie Engineering the Agentic Stack zich bevindtWaar elk deel van de serie Engineering the Agentic Stack zich bevindt

Parts 3 en 4 delen step 3, en die overlap is precies het argument om ze als één programma te behandelen. Dezelfde laag harness code die een malformed argument afwijst, wijst ook een call af die wel is toegestaan maar nog niet approved. Wanneer je ze over twee services splitst, gaan die twee rejections van elkaar afwijken en kan een call die op de ene plek schema validation doorstaat, ergens worden geautoriseerd waar het schema nooit is gezien.

De split blijft relevant voor debugging: een edit in het verkeerde bestand is een Part 4 path rule, geen Part 2 retrievalprobleem. Een sectie verderop zet dit om in een routing table.

OpenAI’s eigen harness-engineering case study beschrijft een bootable application instance voor elke worktree. Het team koppelde ook browser automation aan de agent environment en maakte logs, metrics en traces beschikbaar.

Een task zoals “geen span in deze vier critical user journeys duurt langer dan twee seconden” werd testbaar omdat de agent de applicatie kon uitvoeren en dezelfde signals kon opvragen die een engineer zou inspecteren. De case study is product-specific. Wat overdraagbaar is, is de voorwaarde achter het resultaat: de applicatie en haar performance signals moesten beschikbaar zijn binnen de agent environment.

Lopopolo, de auteur van die case study, onderhoudt een field guide voor harness engineering. Daarin worden de twee levers benoemd die dit artikel gebruikt: houd het model en de coding agent vast als een black box en engineer de context en tools eromheen. Zijn framing verklaart ook waarom zo veel van de harness uit gewone code bestaat.

De quality bar, procedures, exception history en authority relationships van een organisatie vallen buiten wat een general model kan weten. De harness maakt ze beschikbaar als repository instructions, permission rules en acceptance checks. Elke geaccepteerde run kan zijn lessons terugvoeren naar die artifacts, in plaats van erop te vertrouwen dat de volgende session ze opnieuw ontdekt.


Volg de discount change van proposal naar acceptance

Voor de hierboven gedefinieerde discount task stelt het model een wijziging voor van calculate_discount in src/checkout.py. Voordat die edit als progress telt, gebeurt het volgende:

  1. De context builder levert de task, repository instructions, relevante bestanden, eerdere tool results en het huidige plan.
  2. Het model stelt een edit_file call voor met een path en replacement text.
  3. De tool boundary (de harness code tussen proposal en execution) valideert de arguments, controleert het path tegen de toegestane scope en vraagt om approval wanneer de operation dit vereist.
  4. De runtime past de edit toe in de sandbox en retourneert een structured result.
  5. De harness voert pytest tests/test_checkout.py uit, gevolgd door pnpm playwright test tests/checkout_discount.spec.ts, en leest beide exit codes. De browser test controleert de zichtbare korting van $8 op de seeded cart van $80.
  6. De harness beslist wat de results betekenen. Een failed check wordt nieuwe context voor de volgende model turn; een geslaagde run maakt de task een completion candidate.
  7. Een geslaagd resultaat wordt pas completion evidence nadat de harness de command, exit code en de version van het geteste artifact in de trace heeft vastgelegd.

Na step 2 is er nog geen bestand gewijzigd. De harness kan ../../secrets.env afwijzen, approval vereisen voor een destructive command of een run stoppen die zijn budget heeft opgebruikt. Dat is het laatste goedkope moment dat je krijgt. Nadat de tests zijn uitgevoerd, leest de harness zelf hun exit codes. Het model kan zijn eigen edit niet als geslaagd markeren.

De trace moet het voorgestelde path en de replacement text tonen, de permission decision, de gewijzigde bestanden, de geteste commit en beide command results. Een definitief done message zonder die records bewijst niet dat deze wijziging de vereiste checks heeft doorstaan.


Bepaal waar elke rule wordt afgedwongen

De requirement dat tests/checkout_discount.spec.ts slaagt, hoort in deterministic code en niet in de prompt. De harness dispatcht de Playwright command naar de runtime, leest de exit code en weigert de run te beëindigen zolang de test faalt. Een prompt kan het model eraan herinneren de test uit te voeren. De prompt kan het model er niet van weerhouden succes te claimen zonder evidence.

Andere regels passen bij andere lagen:

Plaats de rule inGeschikt voorVoorbeeld
Prompt of skillSearch order, coding conventions en plan formatLees AGENTS.md voordat je checkout code bewerkt
Tool boundaryArgument validation, allowed paths, approvals en tool accessSta writes alleen toe onder src/
Deterministic codeBudgets, timeouts, retries, test exit codes en release gatesHoud de run open zolang de Playwright test faalt
Fresh-context evaluatorVisual review of criteria die human-like judgment vereisenVergelijk een gegenereerd diagram met een geschreven rubric

Tool contracts scheiden proposal van permission

De discount task heeft alleen file edits en test commands nodig. Een state-changing API heeft een ander failure mode, dus voor deze sectie wisselen we van voorbeeld. Stel dat de agent create_test_order kan aanroepen tegen een staging order service tijdens het opzetten van test data. Deze tool is geen acceptance check van de discount task. Hij is hier nuttig omdat een timeout kan verbergen of de service een order heeft aangemaakt.

De tool boundary heeft meer nodig dan een beschrijving in natuurlijke taal. Er is een expliciet tool contract nodig. Part 3 bepleitte dit vanuit het perspectief van het model: duidelijke actions, compacte feedback en recoverable errors. De harness heeft om een andere reden hetzelfde contract nodig. Hij moet zonder het model te vragen beslissen of een call mag worden uitgevoerd en of een failed call mag worden herhaald. Voor create_test_order betekent dit een contract met:

  • gevalideerde arguments, zodat malformed input vóór execution wordt afgewezen
  • een structured result zoals { "order_id": "123", "created": true }, zodat latere checks geen free-form text hoeven te parsen
  • een effect category die vastlegt of de call alleen informatie ophaalt of een file, database record of external service wijzigt. Ook wordt vastgelegd of het veilig is de call te herhalen. Dit label vertelt de harness of een automatic retry work kan dupliceren. De harness kan get_order_status retrien wanneer de service die lookup als read-only definieert. Hij mag create_test_order niet blind retrien, omdat de eerste call de order mogelijk al heeft aangemaakt
  • een timeout- en retry policy, zodat een verloren response geen onbeperkte reeks calls triggert
  • een permission rule die bepaalt welke approval vereist is. Het lezen van order status kan automatisch worden uitgevoerd, terwijl het creëren van een order confirmation kan vereisen

De beschrijving in natuurlijke taal is tekst die aan het model wordt getoond. Die kan zeggen: “Create a test order for checkout verification.” Deze zin helpt het model bepalen wanneer het create_test_order moet voorstellen. De zin autoriseert de call niet. In dit voorbeeld valideert de MCP client van de harness de arguments, past hij zijn eigen rules toe en controleert hij server trust, approval requirements en retry safety voordat er iets wordt gedispatched. Dat is de ordered deny/ask/hook/allow ladder uit Part 4, met één extra vraag: of een call die al is gefaald opnieuw mag worden verstuurd.

Een MCP server publiceert tool descriptions en optionele behavior annotations aan de client. Een defecte of malicious server kan een state-changing tool als harmless beschrijven. Een client die die claim automatisch accepteert, kan create_test_order zonder approval uitvoeren of retrien en zo een duplicate creëren. De MCP specification vereist daarom dat clients tool annotations als untrusted behandelen, tenzij de server zelf trusted is.

De specification schrijft geen universele trust setting voor. Je hebt daarom een expliciete trust policy voor je deployment nodig; een server kan zijn eigen annotations niet trustworthy maken. Die policy bepaalt welke metadata permission- of retrybeslissingen mag beïnvloeden en welke annotations alleen advisory blijven.

Voor het retrien van een state-changing call is replay protection nodig

Part 5 plaatst een idempotency key op elke side-effecting tool call. De harness bepaalt wanneer die key het verschil moet maken. create_test_order creëert de order, maar de HTTP response gaat verloren. De harness ziet een timeout en kan niet bepalen of de server de request heeft voltooid. De call herhalen kan een tweede order creëren.

Een status lookup kan worden geretried wanneer de service die als read-only definieert. Een creation call heeft de key nodig: de client voegt een unique request identifier toe en de service retourneert het eerste resultaat in plaats van een nieuwe order te creëren wanneer die identifier opnieuw wordt gezien. Zonder deze protection moet de harness controleren of de order bestaat of vóór een nieuwe poging om een human decision vragen. AWS documenteert dit patroon in zijn guidance voor idempotent APIs.

Acceptance vereist onafhankelijke evidence

Een succesvolle response van create_test_order bewijst alleen dat de tool data heeft geretourneerd. Het bewijst niet dat een coding task de tests heeft doorstaan. Als een latere browser test afhankelijk is van de staged order, moet de harness het response schema valideren en die test nog steeds uitvoeren voordat de code change wordt geaccepteerd.

Sommige criteria kunnen niet worden teruggebracht tot een exit code. Voor een afzonderlijke visual-design task kan een fresh-context evaluator een rendered page of diagram vergelijken met een geschreven rubric — “fresh-context” betekent hier een tweede model session die start zonder history van de run en de geproduceerde artifacts leest in plaats van het transcript. Vergelijk die evaluator met human reviews voordat je hem completion laat gateen.


Een migratie van een payment adapter heeft een handoff nodig

We wisselen opnieuw van task, maar blijven in de fictieve store repository. De agent moet checkout nu migreren van payment adapter v1 naar v2. Het werk omvat de checkout handler, payment client, configuration en tests, en kan daarom langer duren dan één model session — één doorlopende periode van model context, beëindigd door een restart of door een bewuste fresh start in plaats van voortgezet.

Voordat de eerste session zijn contextlimiet bereikt, heeft deze meerdere bestanden gewijzigd, een lokale payment sandbox gestart en tests/payment_migration.spec.ts falend achtergelaten. Die browser acceptance test voltooit één payment via adapter v2 en verifieert de vastgelegde provider ID. Een conversation summary kan de volgende model session oriënteren, maar kan de sandbox niet herstarten en niet bewijzen welke bestanden momenteel gewijzigd zijn.

De volgende session moet drie dingen herstellen:

Wat moet worden hersteldWat het omvatHoe het kan misgaan
Conversation historyMessages, tool calls en geretourneerde resultsOude details verdringen de huidige task
Working environmentFiles, payment sandbox en browser-test stateHet transcript zegt dat een service draait nadat die is gestorven
Task progressPlan, voltooide checks, pending approval, next actionDe volgende session herhaalt afgerond werk

Compaction vervangt oudere messages door een kortere summary, zodat de huidige session kan doorgaan. Een progress handoff legt vast wat de volgende session nodig heeft: de huidige branch, gewijzigde bestanden, het laatste test command en de output daarvan, plus de volgende onopgeloste stap.

Een handoff file is document memory, geschreven voor één reader — de volgende model session — en is een ander artifact dan de checkpoint die de runtime herstelt. De checkpoint beantwoordt waar de execution is gestopt. De handoff beantwoordt wat het werk betekent en wat nog resteert. Herstel een checkpoint zonder handoff en de volgende session krijgt een resumable process, maar geen idee welk van de vier aangeraakte gebieden — handler, client, configuration, tests — al klaar is. Dat leidt tot duplicate work.

Als de oude conversation stale assumptions bevat, kan de harness een fresh model session starten met die handoff en de huidige workspace. Het vervangen van een gecrashte worker en het herstellen van diens processen is een afzonderlijke runtime recovery task.

Een kleine documentation edit heeft mogelijk geen van deze mechanisms nodig. De payment migration heeft een handoff nodig zodra het werk over meerdere sessions loopt, omdat de volgende model session zowel de workspace als de task status moet reconstrueren.

Anthropics experimenten met long-running coding agents gebruikten git history en een progress file tussen sessions. Anthropics latere harness-design report scheidt compaction van een fresh-context handoff en rapporteert dat handoffs orchestration, token use en wall time toevoegen, zonder cijfers te publiceren die een van die overheads specifiek aan de handoff toeschrijven.


Gebruik traces om drie failures van elkaar te onderscheiden

De volgende drie rijen zijn illustratieve trace sketches, geen gemeten runs of output uit het companion lab. Elke rij toont een andere failure en dus een andere harness response.

Wat de trace vastlegtWat er gebeurdeCorrecte response
De read-only get_order_status call retourneert 503; er is geen state-changing call in flightEen transient lookup faaldeRetry de lookup met een bound en backoff
create_test_order time-out, waarna een status lookup order 123 vindt onder idempotency key checkout-42De service creëerde de order, maar de response ging verlorenRetourneer de bestaande order; creëer geen nieuwe
De edit en unit test slagen, maar de trace bevat geen result voor tests/checkout_discount.spec.ts op de geteste commitVereiste acceptance evidence ontbreektHoud de run open en dispatch de browser acceptance test

Een failure die transient lijkt, maakt niet elke call veilig om te retrien. De eerste rij is een read-only lookup. De tweede rij is een state-changing request; daar bepalen de idempotency key en de server-side status of nog een creation attempt is toegestaan. De derde rij is helemaal geen tool failure; de harness heeft de evidence die nodig is om de discount change te accepteren nog niet verzameld.

Een chat transcript legt vast wat het model zag. Het kan niet bewijzen of de order service een request heeft gecommit voordat de response verdween. Het transcript is het verslag van de agent; de trace laat zien wat de machine daadwerkelijk heeft gedaan. Wanneer de twee elkaar tegenspreken, vertrouw dan de trace. De trace moet de client call, approval decision, idempotency key, server result of status lookup, tested commit en acceptance-test result aan elkaar koppelen. Die fields vertellen de harness op welke van de drie paths hij zit.

Terugkerend symptoomKleine wijziging om te proberenWat je meet
Read-only lookups falen transientBounded retry met backoffRecovery rate, extra calls, wall time
Hervatte sessions herhalen afgerond werkStructured progress handoffDuplicate tool actions na resume
Vereiste tests ontbreken bij completionFail-closed acceptance gateTasks die zonder alle vereiste checks worden geaccepteerd
Visual defects overleven deterministic checksFresh-context evaluator met een rubricGevonden defects, false rejections, review time
De agent bewerkt bestanden buiten zijn scopeStriktere tool permissionBlocked calls en manual overrides
Recalled memory verdringt de huidige taskBeperk recalled facts; rank vóór injectieTokens voor recall, voltooide tasks, cost per task

Voordat je een component toevoegt, benoem je de terugkerende failure die het moet verminderen en het getal dat je gaat volgen. Verwijder het component als een gecontroleerde vergelijking dat getal niet genoeg verbetert om de kosten ervan terug te verdienen. De meeste harnesses die ik heb gezien groeien anders: iemand stuit op een slechte run, voegt een guard toe en die guard blijft voor altijd staan omdat niemand kan aantonen dat hij veilig verwijderd kan worden. Zo eindig je met een loop waar niemand meer aan wil komen.

Die terugkerende failures omzetten in een versioned regression suite is een afzonderlijke taak. Ik heb dat apart uitgewerkt in AI Agent Evaluation in Production.


Meet één wijziging tegelijk

Een ablation meet of een harness component het verwachte effect veroorzaakt door dat component te wijzigen of te verwijderen, terwijl de rest van het experiment gelijk blijft. Helpt editor linting dit model bijvoorbeeld op deze task suite?

Gebruik het volgende protocol:

  1. Zet de model version, task instances, environment, grader en prompts buiten het geteste component vast.
  2. Geef beide varianten hetzelfde totale token-, tijd- en dollarbudget.
  3. Kies het aantal trials of de stopping rule voordat je de vergelijking uitvoert.
  4. Voer in beide varianten dezelfde task instances uit. Omdat model outputs variëren, herhaal je elke task meerdere keren.
  5. Rapporteer het gemiddelde samen met de spreiding of het confidence interval.
  6. Tel elke gestarte trial mee, inclusief timeouts, policy stops, harness crashes en evaluator failures.

Alleen success rate kan een duur component verhullen. Meet minimaal het aantal broken tasks dat als complete wordt geaccepteerd, cost en wall time per completed task, tool errors, duplicate orders, review minutes en manual permission overrides. Kies de metric die de echte kosten voor je product draagt. Een stijging van twee punten in completed tasks is een slechte trade als daardoor je review queue verdubbelt.

Een paired payment-migration experiment maakt de progress handoff meetbaar. Elk control/treatment-paar start vanaf dezelfde repository commit en seeded checkpoint, met hetzelfde model, dezelfde task, grader en hetzelfde totale budget. De handoff is de enige switch. De primary metric telt duplicate tool actions na resume: een action is duplicate wanneer de operation en het artifact overeenkomen met een step die de vorige session al had voltooid.

Een paired ablation test van de progress handoffEen paired ablation test van de progress handoff

Het SWE-agent paper fixeert GPT-4 Turbo op de 300-task SWE-bench Lite split en rapporteert 18,0% resolved met zijn volledige interface, tegenover 11,0% voor een shell-only agent met een worked demonstration en 7,3% voor dezelfde agent zonder demonstration. De headline gap van 10,7 procentpunt in het paper wordt gemeten ten opzichte van die baseline van 7,3%; Part 3 werkt dezelfde drie cijfers uit vanuit het perspectief van interface design. Het paper wijzigde ook afzonderlijke interface features:

Interface changeResolved
Volledige SWE-agent interface (reference, unchanged)18,0%
Editor zonder linting15,0%
Volledig bestand in plaats van een viewer van 100 regels12,7%
Volledige observation history in plaats van de laatste vijf15,0%

Deze cijfers horen bij dat model, die benchmark en de cap van $4 per task. De drie rijen onder de reference zijn de nuttige one-feature tests: in elke test werd één interface feature gewijzigd, terwijl het model en de evaluation setup gelijk bleven.

LangChain publiceerde een bredere fixed-model comparison voor deepagents-cli. Daarin wordt een stijging op Terminal-Bench 2.0 van 52,8% naar 66,5% gerapporteerd met gpt-5.2-codex vastgezet, terwijl het team de system prompt, tools en middleware wijzigde. De post bundelt meerdere changes en vermeldt geen confidence interval, fixed-total-budget comparison of per-change ablation table. Het resultaat kan daarom niet identificeren welke change hielp. De modelnamen in deze sectie zijn de modellen die elk onderzoek op het moment van uitvoering fixeert; het protocol is overdraagbaar, niet de modellijst.

Anthropics long-running application report is een kwalitatieve, product-specifieke case study en geen controlled benchmark. De applicatie heet RetroForge, een 2D retro game maker; in Sprint 3 controleerde de harness evaluator 27 criteria voor de level editor. Het werk begon op eerdere Opus-modellen. Toen Opus 4.6 verscheen, verwijderde het team harness components één voor één om te zien welke door het nieuwere model overbodig waren geworden. Het rapport stelt dat evaluator calls overhead werden op tasks die Opus 4.6 betrouwbaar zelfstandig kon voltooien, maar nog steeds hielpen bij de grens van wat het model aankon. Het voorbeeld is een reden om oude scaffolding opnieuw te valideren wanneer het model verandert; het schat geen algemene effect size.


Houd de harness bewerkbaar nadat hij zijn plaats heeft verdiend

Ablation houdt een harness klein, maar de code kan langer meegaan dan het model waarvoor hij is getuned. Een request zoals “mask secrets in every capture path” beschrijft gedrag, geen bestand. In een production harness kan dat gedrag meerdere execution stages en shared state omvatten. Voordat je dit veilig kunt wijzigen, moet je elke implementatiesite vinden — en hetzelfde geldt voor de coding agent waaraan je de taak uitbesteedt.

Een preprint uit 2026 van Wang et al., het Harness Handbook, noemt deze zoektocht behavior localization. Het handbook bouwt een behavior-centric map van de harness codebase. Static analysis, waarvoor geen model calls nodig zijn, extraheert een program graph, waarna een LLM de units in execution stages organiseert.

De maintainer of coding agent begint met een system overview, opent de relevante execution stage en daalt af naar source-grounded entries voor een function of file. Een state register legt vast waar shared state tussen stages wordt geschreven en gelezen. Deze hiërarchie houdt het overzicht klein en behoudt tegelijk een pad naar de source.

Freshness is een afzonderlijke rule. Elke locator moet resolven tegen de live repository. Het handbook bevriest stale entries in plaats van te gokken, en elke non-empty diff synchroniseert de entries die erdoor worden beïnvloed opnieuw.

Het diagram comprimeert de modification loop: een behavior-only request daalt door de levels van het handbook, elke kandidaat-locator wordt vóór het schrijven van het plan tegen de live repository geverifieerd en elke toegepaste diff synchroniseert de map opnieuw.

Een behavior change door een harness handbook routenEen behavior change door een harness handbook routen

De Handbook evaluation volgt het protocol dat dit artikel bepleit. De evaluatie omvat twee open-source harnesses: Terminus-2 (zes Python-bestanden) en de Codex monorepo (2.267 Rust-bestanden). In beide gevallen verkende een read-only planner powered by DeepSeek-V4-Pro de repository direct of via het handbook. Requests, repository, tool permissions en decoding waren in beide arms identiek. Drie judges (GPT-5.5, Opus 4.8, DeepSeek-V4-Pro) scoorden elk edit plan op localization, scope control en reasoning — let op: een van de judges is hetzelfde model dat de plans produceerde:

HarnessBaseline win rateHandbook-assistedPlanner tokens
Terminus-2 (6 files)26,7%45,6%−8,6%
Codex monorepo (2.267 files)28,3%38,3%−12,7%

De handbook-assisted planner won in beide repositories vaker en gebruikte minder planner tokens. De voorwaarden blijven aan dat resultaat verbonden: drie LLM judges scoorden edit plans die door één planner model voor twee harnesses waren geproduceerd. De study evalueerde plans, geen uitgevoerde diffs of production defect rates.


Probeer de methode in het companion lab

Het harness-demo project op commit 517353f3 is een kleine, deterministische exercise met 12 generieke synthetic tasks voor code changes zoals fix-parser-edge-case, split-large-module en wire-browser-test. Het implementeert de fictieve store repository niet.

Elke task fixture declareert een difficulty plus vier boolean conditions: een flaky tool, lost progress, een gemiste implementation gap en ambiguous completion. De simulator leidt een vijfde condition af voor moeilijke tasks die ook een progress file nodig hebben: zonder context_reset bewaart compaction stale assumptions. Een deterministic grader markeert een task alleen als passed wanneer de geselecteerde configuration elke toepasselijke condition afhandelt. Er draait geen model of external service.

De commands beantwoorden verschillende vragen:

  • make check draait Ruff en zeven unit tests, inclusief de validator die elk ablation pair afwijst dat meer dan één component wijzigt.
  • make run print een cumulative teaching matrix en vervolgens vijf geldige leave-one-component-out comparisons.
  • make failures noemt voor elke failed task de niet-afgehandelde condition. De volledige harness moet eindigen met all synthetic tasks pass.
make check
make run
make failures

De causal section van make run ziet er als volgt uit:

component                 control  treatment  delta
retry_policy              8/12     12/12       +4
progress_handoff          7/12     12/12       +5
evaluator                 8/12     12/12       +4
fail_closed_acceptance    7/12     12/12       +5
context_reset            10/12     12/12       +2

Voor elke rij is de control de volledige configuration met één verwijderd component; de treatment herstelt alleen dat component. De eerdere cumulative matrix is nuttig voor orientation, maar sommige aangrenzende rijen voegen meerdere components tegelijk toe en kunnen daarom geen oorzaak identificeren.

Het lab valideert elk gedeclareerd pair voordat het wordt uitgevoerd. De regression tests bevatten ook een bewust ongeldig pair dat retry policy en evaluator tegelijk wijzigt; de validator wijst dit af.

Het lab vergelijkt alle vijf component fields wanneer het een pair valideert. Dit runnable excerpt toont dezelfde guard op één geldig progress-handoff pair:

from dataclasses import dataclass, fields

@dataclass(frozen=True)
class Config:
    progress_handoff: bool = False
    evaluator: bool = False
    retry_policy: bool = False
    fail_closed_acceptance: bool = False
    context_reset: bool = False

def changed_components(control: Config, treatment: Config) -> tuple[str, ...]:
    return tuple(
        field.name
        for field in fields(control)
        if getattr(control, field.name) != getattr(treatment, field.name)
    )

control = Config(progress_handoff=False, evaluator=True, retry_policy=True)
treatment = Config(progress_handoff=True, evaluator=True, retry_policy=True)
assert changed_components(control, treatment) == ("progress_handoff",)

Welke layer open je wanneer een run foutgaat?

De serie liep van inner naar outer, en hier betaalt die volgorde zich uit. Een failed agent run heeft meestal één owner:

Wat de run deedWaar de fix zitPart
Koos een slechte volgende stap terwijl de juiste informatie al beschikbaar wasReasoning loop of het model1
Herhaalde werk of verloor een beslissing die een uur eerder was genomenContext assembly en handoffs2
Kon de benodigde action niet uitdrukken of interpreteerde een geretourneerd resultaat verkeerdTool contract3
Deed iets wat hij nooit had mogen kunnen doenPermission rules4
Verloor alles toen een worker midden in een call stierfSession, checkpoint, sandbox5
Verklaarde werk als geslaagd dat niet was uitgevoerdAcceptance checks en traces6

Vier van deze zes rijen zijn harness code. Rij 5 is de onderliggende runtime en rij 1 is de enige die een prompt nog kan beïnvloeden.


Begin met één loop en één acceptance check

Ik zou een coding-agent harness starten met één capable model, repository instructions, een paar narrow tools, een sandbox en één expliciete acceptance test. Ik zou tool calls, results, costs en die final test in één trace vastleggen, zodat de eerste bruikbare failures zichtbaar zijn zonder ze uit terminal logs en chat transcripts te moeten reconstrueren. Dit is een voorgestelde baseline, geen evidence uit een deployed system.

Voeg daarna alleen toe wat een trace rechtvaardigt. Leg vast wie elk component onderhoudt, hoeveel tokens of seconden het toevoegt en welke regression test het verwijderen ervan na een model upgrade zou rechtvaardigen.

Zes maanden later moet iemand die progress_handoff=True ziet, de failed traces kunnen vinden die de toevoeging ervan rechtvaardigden, evenals de regression cases die het nog steeds in stand houden. De traces verklaren waarom het component bestaat; een actuele behavior map verklaart waar je het moet aanpassen.

Als je hier via een search bent beland, hebben de vijf voorgaande artikelen een systeem rond een reasoning loop opgebouwd:

  1. De loop kiest de volgende stap.
  2. Memory levert context en een echte Postgres checkpoint store bewaart die.
  3. Tool contracts definiëren actions en de result shapes die latere checks kunnen lezen.
  4. Security voegt de deny hook en stop-hook validator toe. Beide blijven in het voorbeeld schetsmatig, maar markeren de control points.
  5. De runtime houdt het proces actief over sessions en failures heen.

De serie voegde ook een MCP sidecar toe om te laten zien waar data-provider tokens thuishoren en een evaluator node die het draft report controleert voordat een human het ziet. Dat zijn gewone codecomponenten rond een model call. De router is om dezelfde reden harness code: hij kiest het reasoning pattern voordat de reasoning loop start.

Je volgende stap is één kleine loop instrumenteren. Leg tool calls, results, costs en één expliciete acceptance test vast. Voeg pas één control toe nadat een trace de failure toont die deze adresseert. Vergelijk hem met een fixed control en verwijder hem wanneer de gemeten benefit verdwijnt.


References


De code van de Market Analyst Agent staat op GitHub.