Hoe Agents Never Sleep werkt
Een doorloop van één onbewaakte run, van begin tot eind — het mechanisme, geen verkooppraatje.
Definitie
Wat is onbewaakte agent-uitvoering?
Onbewaakte agent-uitvoering is een coding agent die een backlog aan tickets afwerkt zonder dat iemand elke stap volgt — geen mens aanwezig om een verduidelijkende vraag te beantwoorden. Het werkt alleen als de agent een staande regel heeft voor wat te doen zodra hij twijfelt: aannemen en doorgaan, die ene beslissing uitstellen en doorgaan naar het volgende ticket, of de hele run stoppen. Agents Never Sleep (ANS) is de governance-laag die die regel levert als een duurzaam, afgedwongen contract, zodat één dubbelzinnig ticket om 2 uur 's nachts nooit de andere negenendertig bevriest.
1. Launch-preflight — voordat er ook maar één token wordt uitgegeven
Een headless run start via de launcher (bin/ans-run), een deterministische GO/NO-GO-poort die draait voordat de agent-CLI opstart — want tegen de tijd dat de eigen preflight-checks van de agent draaien, zijn de eerste tokens al uitgegeven aan een run die misschien gedoemd is.
De launcher controleert, in volgorde: config trust (het bestand .claude/agents-never-sleep.json van de repo moet één keer per gebruiker expliciet vertrouwd zijn, gekoppeld aan zijn SHA-256 — een nieuwe of gewijzigde config zonder iemand in de buurt om goed te keuren is een NO-GO); identiteit (een geconfigureerde doelgebruiker, nooit een onbewaakte run die als root blijft draaien); agentselectie (een benoemd preset, geverifieerd met een echte --version-probe zodat flag-drift wordt opgevangen vóór de uitgave, niet erna); en een lock op de working tree — een niet-blokkerende flock(2) zodat twee gelijktijdige starts op dezelfde repo altijd precies één winnaar opleveren, automatisch vrijgegeven door de kernel bij elke crash.
Exitcodes zijn eenduidig: 0 gestart, 64 NO-GO, 65 de working tree is al bezig. Autonomie-vlaggen — de permission-modus die een losgekoppelde run daadwerkelijk laat doorgaan zonder vast te lopen bij een goedkeuringsprompt — zijn nooit een standaard; een preset wordt pas lanceerbaar zodra een mens precies heeft bevestigd wat die vlag toestaat.

2. De per-ticket state machine
Eenmaal draaiend, drijft de agent een lus van twee commando's aan tegen de harness: next vraagt om één ticket om aan te werken, complete legt vast wat er is gebeurd. Elke aanroep is een verse subprocess boven duurzame, atomair weggeschreven status — een crash tussen de twee verliest niets, omdat niets alleen in het geheugen leeft.
Elk ticket eindigt in precies één van zeven uitkomststatussen, zo gekozen dat de reactie de ochtend erna nooit dubbelzinnig is:
- DONE — geïmplementeerd, deterministische poort groen.
- DONE_LOW_CONFIDENCE — poort groen, maar de gedelegeerde review van een risicovolle diff uitte zorgen, gaf een fout, of draaide nooit; heeft daglicht-review nodig.
- PARKED_DECISION — één beslissing uitgesteld naar een mens; de run ging door.
- PARKED_FOUNDATIONAL — een fundamentele onduidelijkheid; afhankelijke tickets worden in quarantaine gezet tot ze is opgelost.
- BLOCKED_ENV — de omgeving blokkeerde voortgang (een git-lock, een niet-draaibare poort) — niet de schuld van de agent.
- FAILED_RETRYABLE — een poort ving een bug die de diff introduceerde; de wijziging is teruggedraaid; veilig om opnieuw te proberen.
- FAILED_BUG_IN_AGENT — herhaalde fouten wijzen op een systematisch probleem; heeft een menselijke blik nodig.
3. Beslissen — dan pas implementeren
Voordat er een bestand wordt aangeraakt, classificeert de harness de blast radius van het ticket en beslist PROCEED, PARK of HALT. Alleen een PROCEED-ticket bereikt de agent voor implementatie; een PARK wordt vastgelegd vóór elke wijziging, want de beslissing zelf is het product van het werk.
Zodra een PROCEED-ticket wordt overgedragen, bewerkt de agent — de uitvoerder in dit ontwerp — precies de bestanden van dat ene ticket. De harness snapshot eerst de working tree, zodat wat de agent ook doet, ongedaan gemaakt kan worden.

4. De deterministische poort — de enige harde blokkade
Een poort is een shell-commando — jouw testsuite — dat na de wijziging draait. Exit 0 is groen; niet-nul is rood. Dit is het enige in de hele run dat een ticket hard kan blokkeren; al het andere (een gedelegeerde modelreview, een specialistische blik) is adviserend en kan alleen een vertrouwensstempel onthouden, nooit werk terugdraaien of de run stoppen.
Een rode poort wordt geclassificeerd, niet enkel gerapporteerd: een fout die duidelijk door de diff is geïntroduceerd, draait terug naar de laatste groene commit en wordt vastgelegd als FAILED_RETRYABLE; een fout die pre-existent, flaky of omgevingsgebonden lijkt, verlaagt het vertrouwen maar behoudt het werk; een poort die helemaal niet kan draaien (een lock, een timeout, een niet-interactieve prompt die niet te beantwoorden is) wordt vastgelegd als BLOCKED_ENV — nooit een stille stop. ANS verwijdert of slaat nooit een falende test over om groen te forceren.
---
id: fix-flaky-webhook-retry
title: Retry webhook delivery on 5xx with backoff
blast_radius: medium # optional hint — the harness auto-classifies from the diff
gate: pytest tests/webhooks/ -x
---
The webhook sender should retry on 5xx responses with exponential backoff
(max 3 attempts). Cover it with a test that simulates two 500s then a 200.
Illustratief voorbeeld — een ticket is een Markdown-bestand met een optioneel YAML-front-matterblok; de tekst zelf is het enige verplichte onderdeel, en gate benoemt het commando dat voor dat ticket slagen of falen bepaalt.
5. ASK / PARK / HALT — de beslispunten
Drie verschillende reacties op onzekerheid, nooit samengevoegd tot één. ASK is verboden zodra een run onbewaakt is — er is niemand om te antwoorden, dus het wordt automatisch omgezet in PARK. Dat laat twee echte keuzes over:
- PARK — dit ene ticket of deze beslissing uitstellen, en direct doorgaan naar het volgende onafhankelijke ticket. Dit is normaal en gezond, geen stop. Een park legt altijd vast waarom, de kandidaat-interpretaties, en de exacte menselijke beslissing die de ochtend erna wacht. Echte voorbeelden die Hard-PARKen: welke richting een database-migratie op moet, een wijziging aan een publiek of gedeeld API-contract, alles wat een security- of tenant-isolatiegrens raakt, en alles met geld, facturatie of prijsstelling.
- HALT — de hele run stoppen. Gereserveerd voor werkelijk onomkeerbaar gevaar zonder enig vangnet — bijvoorbeeld geen versiebeheer aanwezig en ook niet aan te maken. HALT is bewust zeldzaam; de meeste onzekerheid is een PARK, geen HALT.
De discipline achter de keuze is blast radius: naamgeving, interne structuur, logformulering, of een keuze tussen twee gelijkwaardige lokale implementaties mag PROCEEDen (aannemen, loggen, doorgaan, omkeerbaar). Alles met een grote blast radius, of alles wat werkelijk niet te classificeren is, wordt geparkeerd. Een verkeerd geparkt klein item kost een beslissing van vijf seconden 's ochtends; een verkeerd aangenomen groot item kost een nacht verkeerd werk in de verkeerde richting.

6. De watchdog — bevriezing, dan een hervatbare herstart
Een vastloper is een ander soort fout dan een stop: het proces leeft, doet niets, en geen enkele hook die op een voortijdige stop let, kan het zien. Een aanhoudende golf van provider-overload is de realistische oorzaak — de run is er nog, maar zijn heartbeat is verouderd.
De watchdog is een sidecar die het onbewaakte commando als child-proces draait en zijn heartbeat-bestand pollt. Zodra de heartbeat een geconfigureerde drempel voorbij verouderd raakt, doodt hij het child-proces en herstart het — en omdat de ANS-status duurzaam is, hervat de herstart precies waar de run was; de gedeeltelijke wijzigingen van het lopende ticket worden teruggedraaid naar zijn laatste snapshot, dus er gaat niets verloren en niets telt dubbel. Na een gelimiteerd aantal herstarts geeft het een alert en stopt het, in plaats van eindeloos te blijven herstarten. ans-run omwikkelt elke losgekoppelde start standaard met de watchdog.
Dezelfde sidecar ruimt ook zijn eigen gelekte child-processen op — bijvoorbeeld een MCP-server die de agent startte en nooit sloot — strikt op basis van parent-chain-afstamming van het eigen procesnummer van de run, nooit door een procesnaam te matchen (een naam-match zou een niet-verwante run op dezelfde machine kunnen doden). Eerlijke beperking: als de supervisor zelf geforceerd wordt gedood, kan hij achteraf niet meer opruimen, dus dit vermindert procesleakage, het elimineert het niet.
7. Drain — doordraaien tot er niets meer te doen is
De agent blijft next en dan complete aanroepen tot next een eindstatus teruggeeft: DRAINED (de backlog is leeg), HALTED (een HALT-conditie is opgetreden), of LOW_YIELD (een circuit breaker sloeg door omdat de meeste recente tickets parkeren of blokkeren in plaats van afronden — een signaal dat de backlog zelf een menselijke blik nodig heeft, geen extra pogingen). Poging- en luslimieten forceren elk enkel ticket dat anders de hele run zou opbranden naar een park, zodat één vervloekt item niet precies de mislukking kan herhalen waar dit hele ontwerp voor bedoeld is te vermijden.
8. Het ochtendrapport
Als de run eindigt, schrijft hij één gerangschikt rapport (night-report.md) in plaats van jou te laten reconstrueren wat er gebeurd is uit logs. Het vermeldt: wat is klaar en te vertrouwen, wat is klaar maar heeft daglicht-review nodig (een risicovolle wijziging waarvan de gedelegeerde review het niet vrijgaf), wat is geparkeerd — elk met zijn kandidaat-interpretaties en de exacte volgende actie — wat is geblokkeerd door de omgeving, en eventuele blinde vlekken: een afgezwakte garantie, een capaciteit die niet beschikbaar was, een credential die de run niet kon lezen. Een nacht met lage opbrengst wordt duidelijk gemarkeerd in het rapport, zodat "de run is klaar" nooit wordt verward met "het werk is gedaan".