Pipeline-uri și automatizare
Pipeline-urile sunt jumătatea deterministă a Control Center. Un agent decide ce spune; un pipeline decide ce se întâmplă în continuare, la fel de fiecare dată. Un pipeline este un DAG de pași care pot trimite agenți, pot rula scripturi, pot ramifica după o condiție, se pot desfășura în paralel peste o colecție și pot transmite date între pași — persistat, reluabil și cu costuri calculate.
Există din trei motive: să înlănțuie un proces repetitiv cu mai mulți pași, să coordoneze mai mulți agenți în secvență sau în paralel și să reacționeze la ceva ce s-a întâmplat fără ca un om să fie acolo.
Șabloane și rulări
Secțiune intitulată „Șabloane și rulări”Un șablon (PipelineDefinition) este DAG-ul declarativ: pași, intrări
declarate și configurație. El trăiește în baza de date proprie a spațiului de
lucru. O rulare (PipelineRun) este o singură execuție a acelui șablon,
care duce cu ea un sac de stare mutabil, totaluri de cost și de tokenuri și
starea fiecărui pas.
Un șablon are multe rulări. Rulările supraviețuiesc unei reporniri: motorul reîncarcă rulările în curs și le reia de unde s-au oprit.
Două plase de siguranță eșuează deliberat întreaga rulare în loc să reia un pas, pentru că reluarea ar fi mai rea decât oprirea:
- Un pas care a fost
suspendedmai mult desuspendedStepTimeout(24 de ore implicit) așteaptă tichete care nu vor ajunge niciodată într-o stare finală, așa că este marcat ca eșuat pentru a elibera rularea. Excepția esteextras['awaitApproval'] == true— aceea este poarta de aprobare parțialăorchestration.awaitApproval.human.gatenu face excepție. - Un pas declarat
extras: {'idempotent': false}care era în curs când serverul s-a oprit poate să fi aplicat deja efectul secundar (un squash merge, o curățare de worktree). Reluarea lui ar putea aplica efectul de două ori, așa că motorul îl marchează ca eșuat și îți lasă reluarea ție.
Tipul programează, corpul execută
Secțiune intitulată „Tipul programează, corpul execută”Un pas are două părți ortogonale, iar faptul de a le ține separate este ceea ce permite motorului să rămână mic.
Tipul (StepKind) îi spune motorului când poate rula un pas în raport cu
pașii din amonte — acesta este singurul lucru pe care trebuie să îl știe
planificatorul. Există șase: trigger, listen, join, router, forEach și
terminal. Corpul (bodyKey) este munca pe care pasul o execută odată ce
a fost programat. Corpurile sunt closure-uri înregistrate după cheie la pornirea
serverului, așa că mulți pași de tipuri diferite pot împărți un singur corp: de
exemplu, mai mulți pași listen care rulează toți conversation.promptAgent.
Pentru că semantica de programare trăiește în șase tipuri, catalogul de comportamente poate crește fără ca cineva să atingă planificatorul. Vezi Tipuri de pași din pipeline pentru catalogul complet de tipuri, corpuri și configurația lor.
Două consecințe ale designului bazat pe registru merită știute înainte să scrii de mână un șablon:
- Un pas al cărui
bodyKeynu este înregistrat produce un avertisment la încărcarea șablonului și apoi nu se execută niciodată. Nimic nu eșuează zgomotos la rulare. Pașiitriggerșiterminalsunt exceptați de la acest avertisment: trigger-ul rulează totuși corpul no-oppipeline.trigger, ca să se declanșeze ascultătorii din aval; un pasterminalnu are corp și este o santinelă a planificatorului. - Cheile din harta
extrasa unui nod sunt potrivite exact și ignorate în tăcere altfel. Doarextras['spaceId']pune munca unui pas de agent într-o conversație existentă.extras['mode'](chat/plan/review) este citit pe nodul care deschide camera (messaging.createSpace, implicitreview) — un pas de agent ulterior nu poate re-permisiona o conversație în care a intrat. Orice altă ortografiere a oricăreia dintre chei este ignorată.
Stare și flux de date
Secțiune intitulată „Stare și flux de date”Pașii nu se apelează unul pe altul. Citesc și scriu într-un sac de stare
partajat al rulării: un pas își citește intrările după cheie, execută și își
scrie ieșirea înapoi sub propriul outputKey. Un StateReducer îmbină
scrierile concurente când ramuri paralele țintesc aceeași cheie, iar un
TemplateRenderer înlocuiește marcajele {{...}} din prompturi și scripturi cu
date din aceeași stare.
De aceea un pipeline poate fi inspectat ulterior — sacul de stare este întreaga poveste a rulării.
Ce pornește o rulare
Secțiune intitulată „Ce pornește o rulare”Rândurile PipelineTrigger declară când pornește automat un șablon. Există
patru tipuri:
| Trigger | Pornește o rulare când |
|---|---|
| Eveniment de domeniu | Se declanșează un eveniment de domeniu care corespunde — de exemplu PullRequestPublished sau TicketAssigned |
| Program | Devine scadentă o expresie cron/interval |
| Manual | Îl rulezi manual; prezența unui trigger manual activ este ceea ce pune șablonul în selectorul de rulări |
| Webhook | Un sistem extern trimite un POST către /webhooks/<token> pe portul HTTP al serverului |
Trigger-ele de eveniment sunt un set închis: 16 tipuri de evenimente sunt
cunoscute de dispecer și oferite în selector
(EventPayloadMapper.knownEventTypes), iar nimic altceva nu poate porni un
pipeline. Paisprezece dintre aceste 16 au o mapare de payload. Două —
TicketCreated și TicketStatusChanged — nu au niciuna, așa că un trigger pe
oricare dintre ele rămâne activ și nu se declanșează niciodată. Vezi
Evenimente de domeniu pentru
lista completă.
Tokenul de webhook servește și drept secret HMAC: antetul cererii
X-Hub-Signature-256: sha256=<hex> este verificat față de
HMAC-SHA256(body, token), livrările neverificate sunt respinse și nu pot fi
reluate, iar duplicatele sunt eliminate prin dedupe_key. Pentru că rândurile de
trigger sunt unice pe (spațiu de lucru, tip de eveniment, șablon), un șablon
poate deține cel mult un trigger de webhook și câte un trigger pentru fiecare tip
de eveniment de domeniu.
Șabloanele incluse își seamănă trigger-ele manuale și de eveniment activate; trigger-ele programate se seamănă dezactivate, așa că o baleiere recurentă trebuie activată întotdeauna explicit.
Șabloanele care vin dezactivate
Secțiune intitulată „Șabloanele care vin dezactivate”Treisprezece șabloane de pipeline sunt incluse în fiecare spațiu de lucru, iar
opt dintre ele vin dezactivate: external_pr_welcome, cross_review,
ticket_to_pr, pr_triage, pre_merge_gate, release_notes, dep_audit și
pr_digest.
Aceasta este partea care surprinde lumea. Trigger-ele lor sunt create activate,
dar PipelineEngine.start refuză un șablon dezactivat și întoarce null — așa că
trigger-ul se declanșează în gol și nu apare nicio rulare. Șablonul, nu
trigger-ul, este întrerupătorul. Activează-l în Setări → Spațiu de lucru →
Șabloane pipeline înainte să te aștepți ca vreunul dintre cele opt să facă ceva.
Resemănarea unui spațiu de lucru păstrează ce ai ales: setarea ta isEnabled și
rândurile de trigger existente supraviețuiesc. Există exact o excepție
deliberată — programul zilnic al pr_merged_cleanup este activat forțat chiar
dacă era oprit anterior, pentru că este colectare de gunoi. Rândurile de
worktree, partițiile grafului de cod și copiile copy-on-write se acumulează
fără el, iar asta nu a fost niciodată menită să fie o alegere a utilizatorului.
Încă o asimetrie de știut: șablonul inclus pr_review nu mai postează deliberat
pe GitHub. Publicarea unei revizuiri este un pas separat, condiționat de
utilizator, iar un pipeline care ar posta direct ar publica de două ori.
cross_review, ticket_to_pr și pr_triage postează totuși un comentariu
direct pe GitHub când rulează.
Ce știe motorul
Secțiune intitulată „Ce știe motorul”PipelineEngine creează înregistrarea rulării, programează nodul de start care
corespunde tipului de eveniment al rulării și apoi parcurge DAG-ul: pe măsură ce
pașii se încheie, evaluează trigger-ele din aval, rezolvă ramurile de router,
așteaptă îmbinările, aplică politicile de reîncercare și continuarea la eșec și
persistă starea, costul și erorile la fiecare pas. Muchiile care pleacă dintr-un
alt start sunt ignorate pentru rularea respectivă, așa că o declanșare
programată nu așteaptă o condiție doar-manuală.
Ce nu știe este orice despre tichetare, trimitere, GitHub sau mesagerie.
Singurul său punct de extensie este PipelineBodyRegistry. Corpurile
înregistrate acolo sunt cele care dețin AgentDispatchPort, MessagingPort și
suratele lor — de aceea adăugarea unei capacități la pipeline-uri nu înseamnă
niciodată schimbarea planificatorului.
Tratarea erorilor
Secțiune intitulată „Tratarea erorilor”Un pas care eșuează își înregistrează mesajul de eroare și trece în failed.
Dacă pasul declară continueOnFail, pașii din aval continuă, iar eroarea este
pusă deoparte în starea rulării; altfel rularea eșuează. Un pas cu un
StepRetryPolicy este reîncercat mai întâi, cu întârziere crescătoare liniară
sau exponențială, înainte să se aplice oricare dintre cele două.
- Evenimente de domeniu: la ce ascultă un trigger de eveniment
- Orchestrare: un plan pe care îl aprobi, compilat într-unul dintre aceste pipeline-uri
- Tichete și delegare: ce creează și ce finalizează pașii de pipeline
- Tipuri de pași din pipeline: catalogul complet de tipuri, corpuri și configurație
- Construiește primul pipeline
- Creează un șablon de pipeline
- Rulează un pipeline manual
- Monitorizează rulările de pipeline
- Configurează trigger-ele pipeline-urilor