Gå til innholdet

Pipelines og automatisering

Pipelines er den deterministiske halvdelen av Control Center. En agent bestemmer hva som skal sies; en pipeline bestemmer hva som skjer videre, på samme måte hver gang. En pipeline er en DAG av steg som kan sende ut agenter, kjøre skript, forgrene seg på en betingelse, spre seg over en samling og føre data videre mellom steg — vedvarende, gjenopptakbare og kostnadsberegnede.

De finnes av tre grunner: å kjede sammen en repeterende flertrinnsprosess, å koordinere flere agenter i sekvens eller parallelt, og å reagere på noe som har skjedd uten at et menneske var til stede.

En mal (PipelineDefinition) er den deklarative DAG-en: steg, deklarerte inndata og konfigurasjon. Den ligger i arbeidsområdets egen database. En kjøring (PipelineRun) er én utførelse av den malen, med en mutbar tilstandsbeholder, kostnads- og tokentotaler og status per steg.

Én mal har mange kjøringer. Kjøringer overlever en omstart: motoren laster inn kjøringer som er i gang, og tar dem opp der de stoppet.

To sikkerhetsnett feiler med vilje hele kjøringen i stedet for å gjenoppta et steg, fordi gjenopptaking ville være verre enn å stoppe:

  • Et steg som har vært suspended lenger enn suspendedStepTimeout (24 timer som standard), venter på saker som aldri vil nå en terminaltilstand, og det markeres derfor som feilet for å frigjøre kjøringen. Unntaket er extras['awaitApproval'] == true — det er porten for delvis godkjenning, orchestration.awaitApproval. human.gate er ikke unntatt.
  • Et steg som er deklarert med extras: {'idempotent': false} og var i gang da serveren stoppet, kan allerede ha anvendt bivirkningen sin (en squash-sammenslåing, en worktree-opprydding). Å kjøre det på nytt kan anvende den to ganger, så motoren markerer det som feilet og overlater nytt forsøk til deg.

Et steg har to ortogonale deler, og det at de holdes fra hverandre, er det som gjør at motoren kan forbli liten.

Typen (StepKind) forteller motoren når et steg blir kjørbart i forhold til stegene oppstrøms — det er det eneste planleggeren trenger å vite. Det finnes seks: trigger, listen, join, router, forEach og terminal. Kroppen (bodyKey) er arbeidet steget utfører når det først er planlagt. Kropper er closures som registreres med en nøkkel ved serveroppstart, slik at mange steg av ulike typer kan dele én kropp: for eksempel flere listen-steg som alle kjører conversation.promptAgent.

Fordi planleggingssemantikken bor i seks typer, kan katalogen over atferd vokse uten at noen rører planleggeren. Se Pipeline-stegtyper for den fullstendige katalogen over typer, kropper og konfigurasjonen deres.

To konsekvenser av registerdesignet er verdt å kjenne til før du skriver en mal for hånd:

  • Et steg der bodyKey ikke er registrert, gir en advarsel ved innlasting av malen og kjører deretter aldri. Ingenting feiler høylytt under kjøring. trigger- og terminal-steg er unntatt fra den advarselen: triggeren kjører fortsatt den virkningsløse kroppen pipeline.trigger, slik at lyttere nedstrøms utløses; et terminal-steg har ingen kropp og er en vaktpost for planleggeren.
  • Nøkler i en nodes extras-map må samsvare eksakt og ignoreres ellers i stillhet. Bare extras['spaceId'] plasserer arbeidet til et agentsteg i en eksisterende samtale. extras['mode'] (chat / plan / review) leses på noden som åpner rommet (messaging.createSpace, standard review) — et senere agentsteg kan ikke endre tillatelsene til en samtale det ble med i. Enhver annen skrivemåte av disse nøklene ignoreres.

Steg kaller ikke hverandre. De leser fra og skriver til en delt tilstandsbeholder på kjøringen: et steg leser inndataene sine med en nøkkel, utfører og skriver resultatet tilbake under sin egen outputKey. En StateReducer slår sammen samtidige skrivinger når parallelle grener retter seg mot samme nøkkel, og en TemplateRenderer erstatter {{...}}-plassholdere i prompter og skript med verdier fra den samme tilstanden.

Det er derfor en pipeline kan inspiseres i ettertid — tilstandsbeholderen er hele historien om kjøringen.

PipelineTrigger-rader erklærer når en mal starter automatisk. Det finnes fire typer:

Utløser Starter en kjøring når
Domenehendelse En samsvarende domenehendelse utløses — for eksempel PullRequestPublished eller TicketAssigned
Tidsplan Et cron-/intervalluttrykk forfaller
Manuell Du kjører den for hånd; at det finnes en aktivert manuell utløser, er det som gjør at malen vises i kjøringsvelgeren
Webhook Et eksternt system sender POST til /webhooks/<token> på serverens HTTP-port

Hendelsesutløsere er et lukket sett: 16 hendelsestyper er kjent for utsenderen og tilbys i velgeren (EventPayloadMapper.knownEventTypes), og ingenting annet kan starte en pipeline. Fjorten av disse 16 har en nyttelastmapping. To — TicketCreated og TicketStatusChanged — har ingen, så en utløser på noen av dem står aktivert og utløses aldri. Se Domenehendelser for hele listen.

Webhook-tokenet fungerer også som HMAC-hemmeligheten: forespørslens X-Hub-Signature-256: sha256=<hex>-header verifiseres mot HMAC-SHA256(body, token), uverifiserte leveringer avvises og kan ikke spilles av på nytt, og duplikater droppes av dedupe_key. Fordi utløserrader er unike på (arbeidsområde, hendelsestype, mal), kan en mal ha høyst én webhook-utløser og én utløser per domenehendelsestype.

De innebygde malene sås med manuelle utløsere og hendelsesutløsere aktivert; planlagte utløsere sås deaktivert, så en tilbakevendende gjennomkjøring er alltid noe du må velge selv.

Tretten pipeline-maler sås inn i hvert arbeidsområde, og åtte av dem leveres deaktivert: external_pr_welcome, cross_review, ticket_to_pr, pr_triage, pre_merge_gate, release_notes, dep_audit og pr_digest.

Det er denne delen som overrasker folk. Utløserne deres sås som aktivert, men PipelineEngine.start avviser en deaktivert mal og returnerer null — så utløseren fyrer av uten at noe tar imot, og ingen kjøring dukker opp. Det er malen, ikke utløseren, som er bryteren. Aktiver den under Innstillinger → Arbeidsområde → Pipeline-maler før du forventer at noen av de åtte skal gjøre noe.

Å så inn på nytt i et arbeidsområde bevarer det du har valgt: innstillingen din for isEnabled og de eksisterende utløserradene dine overlever. Det finnes nøyaktig ett bevisst unntak — den daglige tidsplanen til pr_merged_cleanup tvinges aktivert selv om den tidligere var av, fordi den er søppelsamling. Worktree-rader, kodegraf-partisjoner og copy-on-write-kopier hoper seg opp uten den, og det var aldri ment å skulle være et valg for brukeren.

Én asymmetri til er verdt å kjenne til: den innebygde pr_review legger med vilje ikke lenger ut noe på GitHub. Å publisere en gjennomgang er et eget steg som brukeren må godkjenne, og en pipeline som la ut direkte, ville publisert dobbelt. cross_review, ticket_to_pr og pr_triage legger derimot fortsatt ut en kommentar rett på GitHub når de kjører.

PipelineEngine oppretter kjøringsoppføringen, planlegger startnoden som samsvarer med kjøringens hendelsestype, og går deretter gjennom DAG-en: etter hvert som steg fullføres, evaluerer den utløsere nedstrøms, løser opp rutergrener, venter på sammenføyninger, anvender retry-policyer og fortsett-ved-feil, og lagrer tilstand, kostnad og feil ved hvert steg. Kanter som forlater en annen startnode, ignoreres for den kjøringen, så en tidsplanavfyring venter ikke på en betingelse som bare gjelder manuelt.

Det den ikke vet, er noe som helst om saksbehandling, utsending, GitHub eller meldinger. Den eneste utvidelsessømmen er PipelineBodyRegistry. Det er kroppene som er registrert der, som holder AgentDispatchPort, MessagingPort og søsknene deres — og det er derfor det å legge til en egenskap i pipelines aldri betyr at planleggeren må endres.

Et steg som feiler, registrerer feilmeldingen sin og går over til failed. Hvis steget erklærer continueOnFail, fortsetter stegene nedstrøms, og feilen lagres i kjøringstilstanden; ellers feiler kjøringen. Et steg med en StepRetryPolicy prøves på nytt først, med lineær eller eksponentiell backoff, før noen av disse to trer i kraft.