Ga naar inhoud

Pipelines en automatisering

Pipelines zijn de deterministische helft van Control Center. Een agent beslist wat er gezegd wordt; een pipeline beslist wat er daarna gebeurt, elke keer op dezelfde manier. Een pipeline is een DAG van stappen die agents kan dispatchen, scripts kan uitvoeren, op een voorwaarde kan vertakken, over een verzameling kan uitwaaieren en gegevens tussen stappen kan doorgeven — vastgelegd, hervatbaar en met kosten bijgehouden.

Ze bestaan om drie redenen: om een repetitief proces met meerdere stappen aan elkaar te rijgen, om meerdere agents na elkaar of parallel te coördineren en om te reageren op iets dat gebeurde zonder dat er een mens aanwezig was.

Een sjabloon (PipelineDefinition) is de declaratieve DAG: stappen, gedeclareerde invoer en configuratie. Het staat in de eigen database van de werkruimte. Een run (PipelineRun) is één uitvoering van dat sjabloon, met een muteerbare statusverzameling, kosten- en tokentotalen en de status per stap.

Eén sjabloon heeft veel runs. Runs overleven een herstart: de engine laadt lopende runs opnieuw en pakt ze op waar ze gebleven waren.

Twee vangnetten laten de hele run bewust mislukken in plaats van een stap te hervatten, omdat hervatten erger zou zijn dan stoppen:

  • Een stap die langer dan suspendedStepTimeout (standaard 24 uur) op suspended staat, wacht op tickets die nooit een eindtoestand bereiken, en wordt daarom op mislukt gezet om de run vrij te maken. De uitzondering is extras['awaitApproval'] == true — dat is de poort voor gedeeltelijke goedkeuring orchestration.awaitApproval. human.gate valt niet onder de uitzondering.
  • Een stap die is gedeclareerd als extras: {'idempotent': false} en nog liep toen de server stopte, heeft zijn neveneffect misschien al toegepast (een squash-merge, een worktree-opruiming). Opnieuw uitvoeren zou het dubbel kunnen toepassen, dus zet de engine de stap op mislukt en laat hij de herkansing aan jou over.

Het kind bepaalt de planning, de body doet het werk

Sectie getiteld “Het kind bepaalt de planning, de body doet het werk”

Een stap heeft twee orthogonale delen, en juist doordat die gescheiden blijven, kan de engine klein blijven.

Het kind (StepKind) vertelt de engine wanneer een stap uitvoerbaar wordt ten opzichte van zijn voorgaande stappen — dat is het enige wat de scheduler hoeft te weten. Er zijn er zes: trigger, listen, join, router, forEach en terminal. De body (bodyKey) is het werk dat de stap uitvoert zodra hij is ingepland. Bodies zijn closures die bij het starten van de server op sleutel worden geregistreerd, zodat veel stappen van verschillende kinds één body kunnen delen: bijvoorbeeld meerdere listen-stappen die allemaal conversation.promptAgent uitvoeren.

Omdat de planningssemantiek in zes kinds zit, kan de catalogus met gedragingen groeien zonder dat iemand de scheduler aanraakt. Zie Pipeline-stapsoorten voor de volledige catalogus van kinds, bodies en hun configuratie.

Twee gevolgen van het registerontwerp zijn de moeite waard om te kennen voordat je met de hand een sjabloon schrijft:

  • Een stap waarvan de bodyKey niet is geregistreerd, geeft een waarschuwing bij het laden van het sjabloon en wordt daarna nooit uitgevoerd. Tijdens runtime mislukt er niets luidruchtig. trigger- en terminal-stappen vallen niet onder die waarschuwing: de trigger voert nog steeds de no-op-body pipeline.trigger uit, zodat luisteraars stroomafwaarts worden geactiveerd; een terminal-stap heeft geen body en is een sentinel voor de scheduler.
  • Sleutels in de extras-map van een node moeten exact overeenkomen en worden anders stil genegeerd. Alleen extras['spaceId'] plaatst het werk van een agentstap in een bestaand gesprek. extras['mode'] (chat / plan / review) wordt gelezen op de node die de ruimte opent (messaging.createSpace, standaard review) — een latere agentstap kan de rechten van een gesprek waaraan hij is toegetreden niet opnieuw instellen. Elke andere spelling van een van beide sleutels wordt genegeerd.

Stappen roepen elkaar niet aan. Ze lezen van en schrijven naar een gedeelde statusverzameling op de run: een stap leest zijn invoer op sleutel, voert uit en schrijft zijn uitvoer terug onder zijn eigen outputKey. Een StateReducer voegt gelijktijdige schrijfacties samen wanneer parallelle takken dezelfde sleutel raken, en een TemplateRenderer vervangt {{...}}-placeholders in prompts en scripts door waarden uit diezelfde status.

Daarom kun je een pipeline achteraf inspecteren — de statusverzameling vertelt het hele verhaal van de run.

Rijen in PipelineTrigger verklaren wanneer een sjabloon automatisch start. Er zijn vier soorten:

Trigger Start een run wanneer
Domeingebeurtenis Een overeenkomende domeingebeurtenis treedt op — bijvoorbeeld PullRequestPublished of TicketAssigned
Schema Een cron- of intervaluitdrukking is aan de beurt
Handmatig Je voert de pipeline met de hand uit; doordat er een ingeschakelde handmatige trigger is, verschijnt het sjabloon in de runkiezer
Webhook Een extern systeem doet een POST naar /webhooks/<token> op de HTTP-poort van de server

Gebeurtenistriggers vormen een gesloten verzameling: de dispatcher kent 16 gebeurtenistypen en biedt ze aan in de kiezer (EventPayloadMapper.knownEventTypes) en niets anders kan een pipeline starten. Veertien van die 16 hebben een payload-mapping. Twee ervan — TicketCreated en TicketStatusChanged — hebben er geen, dus een trigger op een van beide staat ingeschakeld en gaat nooit af. Zie Domeingebeurtenissen voor de volledige lijst.

Het webhooktoken doet tegelijk dienst als het HMAC-geheim: de header X-Hub-Signature-256: sha256=<hex> van het verzoek wordt geverifieerd tegen HMAC-SHA256(body, token), niet-geverifieerde leveringen worden geweigerd en kunnen niet opnieuw worden afgespeeld, en duplicaten worden weggegooid op dedupe_key. Omdat triggerrijen uniek zijn op (werkruimte, gebeurtenistype, sjabloon), kan een sjabloon hooguit één webhooktrigger en één trigger per type domeingebeurtenis bevatten.

De ingebouwde sjablonen worden gezaaid met hun handmatige en gebeurtenistriggers ingeschakeld en hun schematriggers uitgeschakeld, dus een terugkerende sweep zet je altijd zelf aan.

De sjablonen die uitgeschakeld worden geleverd

Sectie getiteld “De sjablonen die uitgeschakeld worden geleverd”

In elke werkruimte worden dertien pipeline-sjablonen gezaaid, en acht daarvan worden uitgeschakeld geleverd: external_pr_welcome, cross_review, ticket_to_pr, pr_triage, pre_merge_gate, release_notes, dep_audit en pr_digest.

Dit is het deel dat mensen verrast. Hun triggers worden ingeschakeld gezaaid, maar PipelineEngine.start weigert een uitgeschakeld sjabloon en geeft null terug — dus de trigger gaat af in het niets en er verschijnt geen run. Het sjabloon is de schakelaar, niet de trigger. Zet het aan bij Instellingen → Werkruimte → Pipelinesjablonen voordat je van die acht ook maar iets verwacht.

Het opnieuw zaaien van een werkruimte behoudt wat je hebt gekozen: je isEnabled-instelling en je bestaande triggerrijen blijven bestaan. Er is precies één bewuste uitzondering — het dagelijkse schema van pr_merged_cleanup wordt geforceerd ingeschakeld, zelfs als het eerder uit stond, omdat het garbagecollection is. Zonder dat schema stapelen worktree-rijen, partities van de codegraaf en copy-on-write-kopieën zich op, en dat was nooit bedoeld als keuze voor de gebruiker.

Nog een asymmetrie die je moet kennen: de ingebouwde pr_review plaatst bewust niet langer berichten op GitHub. Een review publiceren is een aparte stap waarvoor de gebruiker goedkeuring moet geven, en een pipeline die rechtstreeks zou plaatsen, zou dubbel publiceren. cross_review, ticket_to_pr en pr_triage plaatsen bij het draaien nog wel rechtstreeks een opmerking op GitHub.

PipelineEngine maakt het runrecord aan, plant het startknooppunt in dat bij het gebeurtenistype van de run hoort, en loopt daarna de DAG door: naarmate stappen klaar zijn, evalueert hij triggers stroomafwaarts, lost hij routertakken op, wacht hij op joins, past hij herkansingsbeleid en doorgaan-bij-mislukken toe, en legt hij status, kosten en fouten bij elke stap vast. Randen die vanaf een andere start vertrekken, worden voor die run genegeerd, dus een schema-afvuring wacht niet op een voorwaarde die alleen voor handmatige runs geldt.

Wat hij niet weet, is iets over ticketing, dispatch, GitHub of messaging. De enige uitbreidingsnaad is de PipelineBodyRegistry. De bodies die daar zijn geregistreerd, bevatten AgentDispatchPort, MessagingPort en hun soortgenoten — daarom betekent een nieuwe mogelijkheid aan pipelines toevoegen nooit dat je de scheduler moet aanpassen.

Een mislukkende stap legt zijn foutmelding vast en gaat naar failed. Als de stap continueOnFail declareert, gaan de stappen stroomafwaarts verder en wordt de fout in de runstatus bewaard; anders mislukt de run. Een stap met een StepRetryPolicy wordt eerst opnieuw geprobeerd, met lineaire of exponentiële backoff, voordat een van beide van toepassing is.