Agent harness: så bygger vi körningen runt en språkmodell
En agent harness är körningen runt en språkmodell: verktyg, minne, en loop, behörigheter, utvärdering och en budget. Så bygger vi våra egna.
En agent harness är körningen runt en språkmodell när en demo ska klara en riktig uppgift. Modellen föreslår nästa steg. Harnesset avgör vilka verktyg som finns, vad som får ligga i fönstret, om en människa måste godkänna, och vad som skrivs ned när varvet tar slut. Vi bygger den körningen själva och kör den i vårt eget utvecklingsarbete. Ett textsvar är ett anrop. En harness är systemet man kan drifta.
Ett varv har en form
Varje varv går genom namngivna faser: förstå uppdraget, fråga när det är oklart, välja ett arbetssätt, undersöka med skrivskyddade verktyg, planera, utföra, kontrollera resultatet, vänta på en människa, svara, och sedan avsluta, stoppa eller misslyckas. Ett steg som inte hör hemma på den vägen avvisas. Ett svar som redan är givet förblir ett svar.
En ren körning ser ut så här: förstå → välj väg → planera → utför → kontrollera → svara → klar.
Stegets tillstånd skrivs en gång, i slutet, innan nästa steg kan se något. Tillåtelselistan, arbetskatalogen och valet av isolering ligger på anropet och läses om varje steg. Nästa steg följer det anroparen faktiskt skickade.
Verktygsanrop
Ett verktyg är en förmåga med ett namn och ett schema. Modellen får inte katalogen. Vi matchar tillåtelselistan, jokertecken inräknade, mot katalogen som den ser ut just nu, och taket för den listan modellen får se är 128 verktyg. Fönstret ska handla om uppgiften. Resten går att söka fram. Prompten bär en enradig beskrivning av varje verktyg som faktiskt ligger på listan. De längre instruktionerna för en förmåga stannar utanför fönstret tills något ber om dem.
Styrplanet ligger utanför modellen: själva varvköraren, kön, sessionslagret och det råa modellanropet. En orkestrerare får starta en barnsession, fråga efter status och gå igenom trädet. Ett löv förblir ett löv, om det inte själv är märkt som orkestrerare.
När ett varv pausar mitt i en verktygsloop sparar vi assistentens anrop och de matchande resultaten. Nästa generering är fortfarande ett giltigt verktygssamtal. Därför kan ett stopp eller ett godkännande landa mellan verktyg, i stället för inuti en bunt anrop som körningen inte längre ser.
Kontext är en budget. Minnet är ett lager.
Sammansättningen av kontext har inget eget minne. Anroparen skickar meddelandena och modellen. Sammansättningen öppnar varken sessionen eller minneslagret. Den har ett uppdrag: lämna tillbaka ett fönster som ryms. Gränserna kommer från modellens registrerade tak när vi har ett, och från en försiktig standard när vi inte har det. Om räkningen fortfarande ligger över den användbara budgeten efter komprimering anropas inte leverantören. Om fönstret ändå inte ryms stoppas varvet. Att ändå anropa modellen när målet redan trillat ur fönstret är felet vi valde bort.
Anroparen skriver tillbaka komprimeringen som en egen post i transkriptet, märkt så att nästa sammansättning ser att tidigare varv är sammanfattade. En hook som kör efter att fönstret är byggt får byta systemprompten, eller lägga till rader, för just det anropet. Raderna skrivs inte in i transkriptet.
Transkriptet är korttidsminnet. Långtidsminnet är ett eget lager: arbetsanteckningar, observationer och embeddings, under en scope som anroparen själv sätter. Lagret tolkar inte vad den betyder. Efter ett varv kan vi lägga till, skriva om eller ta bort ett faktum, eller koppla ihop två subjekt. Före nästa generering söker en hook i det indexet med det senaste användarmeddelandet. Ett exakt subjekt rankas först, sedan ett länkat subjekt, sedan texten. En träff som bara är text måste passera en likhetströskel innan den injiceras.
Återkallade rader läggs bara på modellanropet. De fogas inte till transkriptet, så en dålig träff kan inte bli sessionens historia. Hooken är öppen vid fel: ett minnesfel gör varvet sämre, och varvet körs ändå. Vi behåller tillståndet för harnesset i de senaste 20 varven i en session och tar bort de äldre varvposterna.
Planering och loopen runt den
Produktionsschemaläggaren skriver ned vad den förstod, och vad som räknas som klart, innan den väljer väg. En oklar förståelse frågar människan och stannar där. En tydlig förståelse namnger vägen: ett direkt svar, ett enda verktygsanrop, en fryst checklista, en reaktiv verktygsloop eller en skrivskyddad undersökning. Undersökningen samlar, sedan klassificerar vi igen. En skrivskyddad fas, och en barnsession märkt som skrivskyddad, nekar skrivningar och kommandoexekvering. Den fasen får inte själv börja ändra något.
Om klassificeraren hoppas över, saknar konfiguration eller svarar med fel, faller produktionsläget tillbaka till den reaktiva loopen, så att ett saknat klassificeringssteg ändå gör arbete. Den som tvingat fram beslut-sedan-handling får inte den reservvägen. Varvet stannar.
En plan som anroparen tvingar fram är ett lager ovanpå schemaläggaren, för den som vill ha en checklista öppnad eller en grind framför den. Modellen får öppna en plan och ändra stegen. Körningen skriver planens brödtext. Utförandet tar sedan ett checkliststeg per varv, så att ett stopp eller ett godkännande kan landa mellan verktyg. Varje steg bär en kontroll mot verktygets resultat: en token, ett innehåller- eller saknas-test, en exitkod, en JSON-kontroll, en kombination av dem, eller ett beviskommando. En mening som modellen skriver om sitt eget arbete ignoreras. En miss planerar om det steget. En andra miss stryker den ofärdiga svansen och behåller stegen som redan hållit.
Parallellt arbete är att starta barnsessioner, med en förälder och barn. En barnsession ärver förälderns val av loop om den inte sätter ett eget, och en tvingad plan följer inte med. Dess återstående varv kan inte överstiga det föräldern har kvar. Djupet är 3 som standard, och en förälder får starta högst fem nya barn i ett varv. Modellen kan inte höja något av taken. Ett senare varv får starta barn igen, upp till samma tak. En förälder med barn som fortfarande kör väntar, i stället för att förklara sig klar. När varje barn är klart sys en rad tillbaka och föräldern fortsätter. Som standard väntar föräldern på alla barn. Den kan också vänta på det första, på ett kvorum, eller övervaka just den gruppen.
En session som fortfarande väntar på en engångsväckning stannar kvar i stället för att avsluta, tills väckningen kommer eller sessionen stoppas. Klar betyder i det läget att väckningen inte har hänt än.
Behörigheter, grindar och isolering
Verktyg är nekade som utgångsläge. En tillåtelselista namnger vad som får köra, och en nekande regel vinner efter det, även när tillåtelselistan är ett jokertecken. En fast regel döljer ändå spendering och styrplan under ett jokertecken: hemligheter, själva godkännandegrinden, det råa modellanropet, en scope-bunden spärr som kan frysa arbetet, och möjligheten att skriva om lagrat tillstånd. Godkännanden och den spärren ligger utanför agentens verktygslista.
Utfallet är ett av tre. Tillåt kör verktyget. Neka blockerar det. Allt annat väntar på en människa, och samma anrop spelas upp igen efter beslutet. En väntan köar inte nästa steg. Godkännandet är bundet till samma scope som sessionen, så ett beslut från ett annat scope kan inte släppa väntan.
En liten mängd oåterkalleliga handlingar frågar en människa även när loopen annars skulle tillåta dem: att slå ihop, att rebassa, att landa en ändring, och att applicera, patcha eller ta bort en resurs i ett kluster.
Filsystemet nås via en beviljad rot. En klassificerare bedömer om sökvägen ligger i scope, och mitten av det spannet öppnar en grind. Skal och git i en isolerad körning stämplas med sandlådan och den beviljade roten. Anroparens arbetskatalog återanvänds inte som skalets aktuella katalog inuti den sandlådan.
Hookar sitter på varvet: före varvet, före och efter generering, före och efter ett verktyg, och efter varvet. Förfaserna och hooken efter varvet avbryter varvet om de nekar, såvida anroparen inte säger något annat. En hook som nekar ett varv som var på väg att bli klart skickar tillbaka det, upp till två omförsök, och sedan stannar varvet. Strukturerad output kontrolleras på samma sätt. Ogiltig JSON räknas inte som klart.
Isolering är en sandlåda för körningen, och en sandlåda per barnsession, som stoppas när varvet tar slut om vi inte uttryckligen behåller den. En egen arbetskopia håller utcheckningen stabil över en paus och över barnen.
Utvärdering, bevis och ett mål
Vi håller tre kontroller isär, eftersom de felar på olika ställen.
En utvärderingsvakt armeras på en session med kriterier och en varvbudget. När ett varv tar slut körs ett test. Godkänt stoppar vakten. Underkänt fortsätter sessionen, med samma verktyg, modell och isolering, tills budgeten är slut, och då stoppas sessionen. Standardtestet söker en delsträng i varvets resultat. Anroparen kan skicka ett eget test när en delsträng är fel mått. Vakten är det som ger sessionen ett varv till. Den är också det som tvingar sessionen att stanna.
Bevis är en dom bunden till en revision. Vi kör ett kommando och lagrar resultatet mot den revisionen, och domen kan blockera en push, en merge eller en landning. Om kontrollen saknas går landningen igenom. Om den finns och domen säger nej blockeras landningen. Människan som måste godkänna är fortfarande grinden. Beviset är maskinkontrollen bredvid den människan.
Ett mål är det hållbara utfallet över flera vågor av arbete. Vakten täcker en session.
Vad vi skriver ned
Varje varv publicerar en start och ett avslut. Avslutet bär en liggare: vilken loop som begärdes, vilken hanterare som faktiskt körde, vad vägvalet blev, vilka verktyg som kördes, vilka som blockerades, tokenantal uppdelat per lager (klassificeraren, planeraren, den reaktiva loopen, utföraren), ett utfall som är lyckat, delvis, ett avstående, eller överlämnat till en människa, och fasvägen. Liggare skrivs bara på, så verktygsköraren och loopen skriver sina egna siffror och lämnar varandras ifred.
Samma innehåll skrivs som ett kvitto när den komponenten finns. Spår korreleras på ett körnings-id. Loggar, spår och mätvärden kan stanna i processen eller lämna via OpenTelemetry. Det lagret beskriver körningen, och det hålls fritt från produktens egna begrepp.
Omförsök, tidsgränser och kostnad
Ett anrop till leverantören försöks om endast så länge inget verkligt innehåll har skickats vidare till anroparen. Standard är två omförsök, med ökande paus mellan försöken, och ett avbrott ställer in det pågående anropet. En ström som blir tyst försöks inte om. Standardgränsen för tystnad är två minuter, och hela strömmen är takad till fem minuter. Att försöka om en tyst ström riskerar att betala två gånger för token som kanske redan har genererats.
Utdatans längd kläms till det stramare av anropet, modellens eget tak och ett mjukt tak. Det mjuka taket är 32 000 token i utdata som standard. Ett anrop kan inte höja modellens maximum. En nivå för hur mycket modellen får tänka, låg, medel eller hög, skickas på varje generering. Medel är standard. Barnsessioner ärver nivån, ett senare meddelande i samma varv kan ändra den, och en väntan behåller nivån den hade.
Tokenanvändning skrivs per lager på kvittot. När leverantören utelämnar en kostnad fäster vi en från modellkatalogen, så att ett varv kan kostnadsberäknas i efterhand från varvets egen post.
De andra budgetarna är strukturella. Ett barn kan inte få fler varv än föräldern har kvar. Förgrening och djup har tak som modellen inte kan ändra. En utvärderingsvakt stannar vid sin varvbudget. En dålig output eller ett nekat avslut försöks om två gånger, sedan stannar varvet. Ett plansteg planeras om en gång, sedan stryks den ofärdiga svansen. Ett fönster som inte ryms når aldrig leverantören.
Där det möter ett uppdrag
Vi kör det här i vårt eget utvecklingsarbete. Samma idéer syns, i mindre skala, när ett team ska få ut en agent eller ge en grupp kodagenter.
En enskild agent som ska gå i drift är en Hephaestus-sprint: ett system, ett golden set, övervakningskrokar, en körjournal och ett beslut om den ska skalas. Ett team som redan använder kodagenter, eller står i begrepp att göra det, går igenom ett AI-kodinförande: kontext i repot, skills, en säkerhetsgräns och en grind i CI innan en ändring landar.
Vanliga frågor
Vad är en agent harness? Det är körningen runt en språkmodell: loopen som anropar verktyg, kontextfönstret, behörigheterna och nedteckningen av vad som hände. Modellen föreslår nästa steg. Harnesset avgör om steget körs.
Varför ligger verktyg bakom en tillåtelselista? En lång lista tränger undan uppgiften och vidgar vad ett felaktigt anrop kan röra. Vi tillåter en avgränsad mängd, döljer spendering och styrplan även för ett jokertecken, och håller allt som inte matchar tills en människa tar ställning.
Hur tar en loop slut? Ett varv har en budget. En förälder kan inte starta barn bortom djupet och taket per varv, och modellen kan inte höja de taken. En misslyckad kontroll planeras om en gång, sedan stryks den ofärdiga svansen. En utvärderingsvakt fortsätter bara tills dess varvbudget tar slut, och då stoppas sessionen.
När räcker en kodassistent? En kodassistent behöver ändå en gräns i repot: vad den får röra, hur en ändring bevisas, och vem som godkänner att den landar. En produktagent som ska gå utan en människa i varje varv behöver resten av harnesset också: isolering, en grind, ett kvitto och en utvärdering som kan avbryta den.
Relaterade tjänster
Tjänster som matchar ämnet
Om ni vill ha hjälp med detta är det här teamen som bygger det.
