Hoppa till innehåll

Pipelines och automatisering

Pipelines är den deterministiska halvan av Control Center. En agent bestämmer vad som ska sägas; en pipeline bestämmer vad som händer härnäst, på samma sätt varje gång. En pipeline är en DAG av steg som kan skicka ut agenter, köra skript, förgrena på ett villkor, sprida ut över en samling och föra data mellan steg – beständig, återupptagbar och kostnadsberäknad.

De finns av tre skäl: att kedja ihop en repetitiv process med flera steg, att samordna flera agenter i följd eller parallellt och att reagera på något som har hänt utan att en människa är närvarande.

En mall (PipelineDefinition) är den deklarativa DAG:en: steg, deklarerade indata och konfiguration. Den bor i arbetsytans egen databas. En körning (PipelineRun) är en enskild körning av den mallen, och bär en föränderlig tillståndsbehållare, summor för kostnad och tokens samt status per steg.

En mall har många körningar. Körningar överlever en omstart: motorn läser in pågående körningar igen och plockar upp dem där de stannade.

Två skyddsnät låter avsiktligt hela körningen misslyckas i stället för att återuppta ett steg, eftersom en återupptagning skulle vara värre än att stoppa:

  • Ett steg som har varit suspended längre än suspendedStepTimeout (24 timmar som standard) väntar på ärenden som aldrig når ett terminalt tillstånd, och därför misslyckas det för att frigöra körningen. Undantaget är extras['awaitApproval'] == true – det är grinden för delgodkännande, orchestration.awaitApproval. human.gate omfattas inte av undantaget.
  • Ett steg som deklarerats med extras: {'idempotent': false} och som var mitt i körningen när servern stoppades kan redan ha tillämpat sin sidoeffekt (en squash-merge, en worktree-städning). Att köra om det skulle kunna tillämpa effekten två gånger, så motorn låter det misslyckas och lämnar återförsöket till dig.

Ett steg har två ortogonala delar, och att hålla dem åtskilda är det som gör att motorn kan förbli liten.

Typen (StepKind) talar om för motorn när ett steg blir körbart i förhållande till sina uppströmssteg – det är det enda schemaläggaren behöver veta. Det finns sex stycken: trigger, listen, join, router, forEach och terminal. Kroppen (bodyKey) är det arbete steget utför när det väl har schemalagts. Kroppar är closures som registreras per nyckel vid serverstart, så många steg av olika typ kan dela på en kropp: flera listen-steg som alla kör conversation.promptAgent, till exempel.

Eftersom schemaläggningssemantiken bor i sex typer kan katalogen av beteenden växa utan att någon rör schemaläggaren. Se Pipeline-stegtyper för den fullständiga katalogen över typer, kroppar och deras konfiguration.

Två konsekvenser av registerdesignen är värda att känna till innan du skriver en mall för hand:

  • Ett steg vars bodyKey inte är registrerad ger en varning vid inläsning av mallen och körs sedan aldrig. Inget misslyckas högljutt vid körning. trigger- och terminal-steg omfattas inte av den varningen: triggern kör fortfarande den verkanslösa kroppen pipeline.trigger så att nedströms lyssnare utlöses; ett terminal-steg har ingen kropp och är en markör för schemaläggaren.
  • Nycklar i en nods extras-mapp matchas exakt och ignoreras i övrigt tyst. Bara extras['spaceId'] placerar ett agentstegs arbete i ett befintligt samtal. extras['mode'] (chat / plan / review) läses på den nod som öppnar rummet (messaging.createSpace, standard review) – ett senare agentsteg kan inte ändra behörigheterna för ett samtal det har anslutit till. Varje annan stavning av någon av nycklarna ignoreras.

Steg anropar inte varandra. De läser från och skriver till en delad tillståndsbehållare på körningen: ett steg läser sina indata per nyckel, kör och skriver tillbaka sin utdata under sin egen outputKey. En StateReducer slår ihop samtidiga skrivningar när parallella grenar riktar sig mot samma nyckel, och en TemplateRenderer ersätter {{...}}-platshållare i prompter och skript med värden från samma tillstånd.

Det är därför en pipeline går att granska i efterhand – tillståndsbehållaren är hela historien om körningen.

Rader av typen PipelineTrigger deklarerar när en mall startar automatiskt. Det finns fyra typer:

Trigger Startar en körning när
Domänhändelse En matchande domänhändelse utlöses – till exempel PullRequestPublished eller TicketAssigned
Schema Ett cron- eller intervalluttryck blir dags
Manuell körning Du kör den för hand; förekomsten av en aktiverad manuell trigger är det som gör att mallen dyker upp i körningsväljaren
Webhook Ett externt system gör en POST till /webhooks/<token> på serverns HTTP-port

Händelsetriggers är en sluten uppsättning: 16 händelsetyper är kända för dispatcharen och erbjuds i väljaren (EventPayloadMapper.knownEventTypes), och inget annat kan starta en pipeline. Fjorton av de 16 har en nyttolastsmappning. Två – TicketCreated och TicketStatusChanged – har ingen, så en trigger på någon av dem ligger aktiverad och utlöses aldrig. Se Domänhändelser för hela listan.

Webhook-token fungerar även som HMAC-hemligheten: begärans header X-Hub-Signature-256: sha256=<hex> verifieras mot HMAC-SHA256(body, token), overifierade leveranser avvisas och kan inte spelas upp igen, och dubbletter filtreras bort med dedupe_key. Eftersom triggerrader är unika på (arbetsyta, händelsetyp, mall) kan en mall ha högst en webhook-trigger och en trigger per domänhändelsetyp.

De inbyggda mallarna sås med manuella och händelsebaserade triggers aktiverade; schemalagda triggers sås inaktiverade, så ett återkommande svep är alltid opt-in.

Tretton pipelinemallar sås in i varje arbetsyta och åtta av dem levereras inaktiverade: external_pr_welcome, cross_review, ticket_to_pr, pr_triage, pre_merge_gate, release_notes, dep_audit och pr_digest.

Det här är delen som förvånar folk. Deras triggers sås aktiverade, men PipelineEngine.start vägrar en inaktiverad mall och returnerar null – så triggern utlöses i tomma intet och ingen körning dyker upp. Det är mallen, inte triggern, som är strömbrytaren. Aktivera den under Inställningar → Arbetsyta → Pipelinemallar innan du förväntar dig att någon av de åtta ska göra något.

Att så om en arbetsyta bevarar det du har valt: din inställning isEnabled och dina befintliga triggerrader överlever. Det finns exakt ett medvetet undantag – pr_merged_cleanups dagliga schema tvingas aktiverat även om det tidigare var avstängt, eftersom det är skräpsamling. Worktree-rader, kodgrafspartitioner och copy-on-write-kopior ackumuleras utan det, och det var aldrig tänkt som ett användarval.

Ännu en asymmetri värd att känna till: den inbyggda pr_review publicerar medvetet inte längre till GitHub. Att publicera en granskning är ett separat steg som en användare styr, och en pipeline som publicerade direkt skulle dubbelpublicera. cross_review, ticket_to_pr och pr_triage publicerar fortfarande en kommentar direkt till GitHub när de körs.

PipelineEngine skapar körningsposten, schemalägger startnoden som matchar körningens händelsetyp och vandrar sedan genom DAG:en: i takt med att steg slutförs utvärderar den nedströms triggers, löser upp routergrenar, väntar på joins, tillämpar återförsökspolicyer och continue-on-fail samt sparar tillstånd, kostnad och fel vid varje steg. Kanter som lämnar en annan start ignoreras för den körningen, så en schemautlösning väntar inte på ett villkor som bara gäller manuell körning.

Vad den inte känner till är något om ärenden, dispatch, GitHub eller meddelanden. Dess enda utökningssöm är PipelineBodyRegistry. Det är kropparna som registreras där som håller AgentDispatchPort, MessagingPort och deras syskon – vilket är varför en ny förmåga i pipelines aldrig innebär att schemaläggaren behöver ändras.

Ett steg som misslyckas registrerar sitt felmeddelande och går till failed. Om steget deklarerar continueOnFail fortsätter nedströms steg och felet sparas undan i körningens tillstånd; annars misslyckas körningen. Ett steg med en StepRetryPolicy görs om först, med linjär eller exponentiell backoff, innan något av det andra träder in.