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.pyverifieert de discount calculation.pnpm playwright test tests/checkout_discount.spec.tsvoegt 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.
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:
| Term | Taak | Coding-agentvoorbeeld |
|---|---|---|
| Model | Stelt tekst, een tool call of een final answer voor | Stelt een edit voor in src/checkout.py |
| Reasoning loop | Kiest de volgende stap uit de beschikbare context | Inspect, edit, test, opnieuw inspecteren |
| Harness | Levert context, valideert proposals, autoriseert ze, dispatcht geaccepteerde calls, legt resultaten vast en controleert completion | Staat edits onder src/ toe en vereist beide genoemde tests |
| Runtime | Voert geaccepteerde calls uit en houdt state buiten het worker process actief | Session 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 deel | Wat het voor deze turn beslist | Waar het in de walkthrough van de volgende sectie werkt |
|---|---|---|
| Part 2 — memory | Welke eerdere state in de prompt terechtkomt | Step 1, de context builder |
| Part 3 — tool use | Welke actions bestaan en hoe een gevalideerd resultaat eruitziet | Argumentvalidatie in step 3 en de result shape in step 4 |
| Part 4 — security | Of deze specifieke call nu mag worden uitgevoerd | Step 3, de path check en de approval decision |
| Part 6 — dit artikel | Of de resulterende evidence de run beëindigt | Steps 5 tot en met 7, de acceptance checks en trace |
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:
- De context builder levert de task, repository instructions, relevante bestanden, eerdere tool results en het huidige plan.
- Het model stelt een
edit_filecall voor met een path en replacement text. - 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.
- De runtime past de edit toe in de sandbox en retourneert een structured result.
- De harness voert
pytest tests/test_checkout.pyuit, gevolgd doorpnpm 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. - 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.
- 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 in | Geschikt voor | Voorbeeld |
|---|---|---|
| Prompt of skill | Search order, coding conventions en plan format | Lees AGENTS.md voordat je checkout code bewerkt |
| Tool boundary | Argument validation, allowed paths, approvals en tool access | Sta writes alleen toe onder src/ |
| Deterministic code | Budgets, timeouts, retries, test exit codes en release gates | Houd de run open zolang de Playwright test faalt |
| Fresh-context evaluator | Visual review of criteria die human-like judgment vereisen | Vergelijk 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_statusretrien wanneer de service die lookup als read-only definieert. Hij magcreate_test_orderniet 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 hersteld | Wat het omvat | Hoe het kan misgaan |
|---|---|---|
| Conversation history | Messages, tool calls en geretourneerde results | Oude details verdringen de huidige task |
| Working environment | Files, payment sandbox en browser-test state | Het transcript zegt dat een service draait nadat die is gestorven |
| Task progress | Plan, voltooide checks, pending approval, next action | De 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 vastlegt | Wat er gebeurde | Correcte response |
|---|---|---|
De read-only get_order_status call retourneert 503; er is geen state-changing call in flight | Een transient lookup faalde | Retry de lookup met een bound en backoff |
create_test_order time-out, waarna een status lookup order 123 vindt onder idempotency key checkout-42 | De service creëerde de order, maar de response ging verloren | Retourneer 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 commit | Vereiste acceptance evidence ontbreekt | Houd 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 symptoom | Kleine wijziging om te proberen | Wat je meet |
|---|---|---|
| Read-only lookups falen transient | Bounded retry met backoff | Recovery rate, extra calls, wall time |
| Hervatte sessions herhalen afgerond werk | Structured progress handoff | Duplicate tool actions na resume |
| Vereiste tests ontbreken bij completion | Fail-closed acceptance gate | Tasks die zonder alle vereiste checks worden geaccepteerd |
| Visual defects overleven deterministic checks | Fresh-context evaluator met een rubric | Gevonden defects, false rejections, review time |
| De agent bewerkt bestanden buiten zijn scope | Striktere tool permission | Blocked calls en manual overrides |
| Recalled memory verdringt de huidige task | Beperk recalled facts; rank vóór injectie | Tokens 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:
- Zet de model version, task instances, environment, grader en prompts buiten het geteste component vast.
- Geef beide varianten hetzelfde totale token-, tijd- en dollarbudget.
- Kies het aantal trials of de stopping rule voordat je de vergelijking uitvoert.
- Voer in beide varianten dezelfde task instances uit. Omdat model outputs variëren, herhaal je elke task meerdere keren.
- Rapporteer het gemiddelde samen met de spreiding of het confidence interval.
- 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.
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 change | Resolved |
|---|---|
| Volledige SWE-agent interface (reference, unchanged) | 18,0% |
| Editor zonder linting | 15,0% |
| Volledig bestand in plaats van een viewer van 100 regels | 12,7% |
| Volledige observation history in plaats van de laatste vijf | 15,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.
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:
| Harness | Baseline win rate | Handbook-assisted | Planner 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 checkdraait Ruff en zeven unit tests, inclusief de validator die elk ablation pair afwijst dat meer dan één component wijzigt.make runprint een cumulative teaching matrix en vervolgens vijf geldige leave-one-component-out comparisons.make failuresnoemt voor elke failed task de niet-afgehandelde condition. De volledige harness moet eindigen metall 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 deed | Waar de fix zit | Part |
|---|---|---|
| Koos een slechte volgende stap terwijl de juiste informatie al beschikbaar was | Reasoning loop of het model | 1 |
| Herhaalde werk of verloor een beslissing die een uur eerder was genomen | Context assembly en handoffs | 2 |
| Kon de benodigde action niet uitdrukken of interpreteerde een geretourneerd resultaat verkeerd | Tool contract | 3 |
| Deed iets wat hij nooit had mogen kunnen doen | Permission rules | 4 |
| Verloor alles toen een worker midden in een call stierf | Session, checkpoint, sandbox | 5 |
| Verklaarde werk als geslaagd dat niet was uitgevoerd | Acceptance checks en traces | 6 |
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:
- De loop kiest de volgende stap.
- Memory levert context en een echte Postgres checkpoint store bewaart die.
- Tool contracts definiëren actions en de result shapes die latere checks kunnen lezen.
- Security voegt de deny hook en stop-hook validator toe. Beide blijven in het voorbeeld schetsmatig, maar markeren de control points.
- 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
- OpenAI, Unrolling the Codex agent loop.
- OpenAI, Harness engineering: leveraging Codex in an agent-first world.
- Lopopolo, Harness engineering: anthology, field guide, and agent context bundle.
- Anthropic Engineering, Effective harnesses for long-running agents.
- Anthropic Engineering, Harness design for long-running application development.
- LangChain, Improving Deep Agents with harness engineering.
- Yang et al., SWE-agent: Agent-Computer Interfaces Enable Automated Software Engineering.
- Wang et al., Harness Handbook: Making Evolving Agent Harnesses Readable, Navigable, and Editable, arXiv:2607.13285, 2026.
- AWS, Making retries safe with idempotent APIs.
- Model Context Protocol, Tools specification.
- Market Analyst Agent Repository
De code van de Market Analyst Agent staat op GitHub.