Pipelines und Automatisierung
Pipelines sind die deterministische Hälfte von Control Center. Ein Agent entscheidet, was er sagt; eine Pipeline entscheidet, was als Nächstes geschieht, jedes Mal auf dieselbe Weise. Eine Pipeline ist ein DAG aus Schritten, die Agenten losschicken, Skripte ausführen, an einer Bedingung verzweigen, über eine Sammlung auffächern und Daten zwischen den Schritten weiterreichen — gespeichert, fortsetzbar und mit Kosten erfasst.
Es gibt sie aus drei Gründen: einen wiederkehrenden Ablauf aus mehreren Schritten zu verketten, mehrere Agenten nacheinander oder parallel zu koordinieren und auf etwas zu reagieren, das geschehen ist, ohne dass ein Mensch dabei ist.
Vorlagen und Läufe
Abschnitt betitelt „Vorlagen und Läufe“Eine Vorlage (PipelineDefinition) ist der deklarative DAG: Schritte,
deklarierte Eingaben und Konfiguration. Sie liegt in der eigenen Datenbank des
Arbeitsbereichs. Ein Lauf (PipelineRun) ist eine Ausführung dieser Vorlage
und trägt eine veränderliche Zustandsmap, die Summen für Kosten und Token sowie
den Status je Schritt.
Eine Vorlage hat viele Läufe. Läufe überstehen einen Neustart: Die Engine lädt noch nicht abgeschlossene Läufe neu und setzt dort an, wo sie stehen geblieben sind.
Zwei Sicherungen lassen den ganzen Lauf absichtlich scheitern, statt einen Schritt fortzusetzen, weil Fortsetzen schlimmer wäre als Anhalten:
- Ein Schritt, der länger als
suspendedStepTimeout(standardmäßig 24 Stunden)suspendedwar, wartet auf Tickets, die nie einen Endzustand erreichen, und wird deshalb fehlgeschlagen, damit der Lauf frei wird. Die Ausnahme istextras['awaitApproval'] == true— das ist das Tor für die teilweise Freigabeorchestration.awaitApproval.human.gateist davon nicht ausgenommen. - Ein Schritt mit
extras: {'idempotent': false}, der mitten in der Ausführung war, als der Server anhielt, kann seinen Seiteneffekt schon angewandt haben (ein Squash-Merge, ein Aufräumen des Worktrees). Ihn noch einmal auszuführen könnte den Effekt doppelt anwenden, also lässt die Engine ihn scheitern und überlässt den erneuten Versuch dir.
Die Art plant ein, der Body arbeitet
Abschnitt betitelt „Die Art plant ein, der Body arbeitet“Ein Schritt hat zwei orthogonale Teile, und sie getrennt zu halten hält die Engine klein.
Die Art (StepKind) sagt der Engine, wann ein Schritt relativ zu seinen
vorgelagerten Schritten lauffähig wird — das ist das Einzige, was der Scheduler
wissen muss. Es gibt sechs: trigger, listen, join, router, forEach und
terminal. Der Body (bodyKey) ist die Arbeit, die der Schritt ausführt,
sobald er eingeplant ist. Bodies sind Closures, die beim Start des Servers unter
einem Schlüssel registriert werden, sodass viele Schritte verschiedener Arten
denselben Body teilen können: mehrere listen-Schritte, die alle
conversation.promptAgent ausführen, zum Beispiel.
Weil die Semantik des Einplanens in sechs Arten steckt, kann der Katalog der Verhaltensweisen wachsen, ohne dass jemand den Scheduler anfasst. Der vollständige Katalog der Arten, Bodies und ihrer Konfiguration steht unter Pipeline-Schrittarten.
Zwei Folgen des Registry-Entwurfs solltest du kennen, bevor du eine Vorlage von Hand schreibst:
- Ein Schritt, dessen
bodyKeynicht registriert ist, erzeugt eine Warnung beim Laden der Vorlage und wird danach nie ausgeführt. Zur Laufzeit scheitert nichts laut. Schritte der Arttriggerundterminalsind von dieser Warnung ausgenommen: Der Trigger führt trotzdem den No-op-Bodypipeline.triggeraus, damit nachgelagerte Listener auslösen; einterminal-Schritt hat keinen Body und ist eine Sentinel des Schedulers. - Schlüssel in der
extras-Map eines Knotens gelten nur bei exaktem Treffer und werden sonst still ignoriert. Nurextras['spaceId']legt die Arbeit eines Agenten-Schritts in einen bestehenden Bereich.extras['mode'](chat/plan/review) wird auf dem Knoten gelesen, der den Bereich öffnet (messaging.createSpace, Standardreview) — ein späterer Agenten-Schritt kann einen Bereich, dem er beigetreten ist, nicht neu berechtigen. Jede andere Schreibweise eines der beiden Schlüssel wird ignoriert.
Zustand und Datenfluss
Abschnitt betitelt „Zustand und Datenfluss“Schritte rufen einander nicht auf. Sie lesen aus einer gemeinsamen Zustandsmap
auf dem Lauf und schreiben hinein: Ein Schritt liest seine Eingaben über den
Schlüssel, führt aus und schreibt seine Ausgabe unter dem eigenen outputKey
zurück. Ein StateReducer führt gleichzeitige Schreibvorgänge zusammen, wenn
parallele Zweige denselben Schlüssel treffen, und ein TemplateRenderer ersetzt
Platzhalter {{...}} in Prompts und Skripten aus ebendiesem Zustand.
Deshalb lässt sich eine Pipeline hinterher nachvollziehen — die Zustandsmap ist die ganze Geschichte des Laufs.
Was einen Lauf startet
Abschnitt betitelt „Was einen Lauf startet“PipelineTrigger-Zeilen legen fest, wann eine Vorlage von selbst startet. Es
gibt vier Arten:
| Trigger | Startet einen Lauf, wenn |
|---|---|
| Domain-Ereignis | Ein passendes Domain-Ereignis eintritt — zum Beispiel PullRequestPublished oder TicketAssigned |
| Zeitplan | Ein Cron- oder Intervallausdruck fällig wird |
| Manuell | Du ihn von Hand startest; ein aktivierter manueller Trigger legt die Vorlage in „Pipeline ausführen“ |
| Webhook | Ein externes System per POST an /webhooks/<token> auf dem HTTP-Port des Servers sendet |
Ereignis-Trigger sind eine geschlossene Menge: 16 Ereignistypen kennt der
Dispatcher, und genau die bietet die Auswahl an
(EventPayloadMapper.knownEventTypes). Nichts anderes kann eine Pipeline
starten. Vierzehn der 16 haben eine Payload-Zuordnung. Zwei — TicketCreated
und TicketStatusChanged — haben keine, ein Trigger auf einen der beiden bleibt
also aktiviert und löst nie aus. Die vollständige Liste steht unter
Domain-Ereignisse.
Das Webhook-Token ist zugleich das HMAC-Geheimnis: Der Header
X-Hub-Signature-256: sha256=<hex> der Anfrage wird gegen
HMAC-SHA256(body, token) geprüft, nicht verifizierte Zustellungen werden
abgelehnt und lassen sich nicht wiederholen, und Duplikate verwirft
dedupe_key. Weil Trigger-Zeilen auf (Arbeitsbereich, Ereignistyp, Vorlage)
eindeutig sind, kann eine Vorlage höchstens einen Webhook-Trigger und einen
Trigger je Domain-Ereignistyp haben.
Die mitgelieferten Vorlagen legen ihre manuellen und Ereignis-Trigger aktiviert an; geplante Trigger werden deaktiviert angelegt, ein wiederkehrender Durchlauf ist also immer eine bewusste Wahl.
Die Vorlagen, die deaktiviert ausgeliefert werden
Abschnitt betitelt „Die Vorlagen, die deaktiviert ausgeliefert werden“In jeden Arbeitsbereich werden dreizehn Pipeline-Vorlagen angelegt, und acht
davon werden deaktiviert ausgeliefert: external_pr_welcome, cross_review,
ticket_to_pr, pr_triage, pre_merge_gate, release_notes, dep_audit und
pr_digest.
Genau das überrascht. Ihre Trigger werden aktiviert angelegt, aber
PipelineEngine.start lehnt eine deaktivierte Vorlage ab und gibt null zurück —
der Trigger läuft also ins Leere, und es erscheint kein Lauf. Der Schalter ist
die Vorlage, nicht der Trigger. Aktiviere sie unter Einstellungen →
Arbeitsbereich → Pipeline-Vorlagen, bevor du von einer der acht etwas erwartest.
Ein erneutes Seeding eines Arbeitsbereichs behält deine Wahl: die Einstellung
isEnabled und die bestehenden Trigger-Zeilen bleiben. Es gibt genau eine
gewollte Ausnahme — der tägliche Zeitplan von pr_merged_cleanup wird zwingend
aktiviert, auch wenn er vorher aus war, weil das die Bereinigung ist. Ohne sie
häufen sich Worktree-Zeilen, Partitionen des Code-Graph und Copy-on-Write-Kopien
an, und das Abschalten war nie als Wahl für dich gedacht.
Noch eine Asymmetrie: Die mitgelieferte pr_review postet absichtlich nicht
mehr nach GitHub. Eine Review zu veröffentlichen ist ein eigener Schritt, den du
freigibst, und eine Pipeline, die direkt postete, würde doppelt veröffentlichen.
cross_review, ticket_to_pr und pr_triage posten bei einem Lauf weiterhin
einen Kommentar direkt nach GitHub.
Was die Engine weiß
Abschnitt betitelt „Was die Engine weiß“PipelineEngine legt den Laufdatensatz an, plant den Startknoten ein, der zum
Ereignistyp des Laufs passt, und geht dann den DAG ab: Sobald Schritte fertig
werden, wertet sie nachgelagerte Trigger aus, löst Router-Zweige auf, wartet auf
Joins, wendet Wiederholungsrichtlinien und das Weiterlaufen bei Fehler an und
speichert bei jedem Schritt Zustand, Kosten und Fehler. Kanten, die von einem
anderen Start ausgehen, ignoriert sie für diesen Lauf, ein Start per Zeitplan
wartet also nicht auf eine Bedingung nur für den manuellen Start.
Von Tickets, Dispatch, GitHub und Nachrichten weiß sie nichts. Ihre einzige
Naht zur Erweiterung ist die PipelineBodyRegistry. Die dort registrierten
Bodies halten AgentDispatchPort, MessagingPort und ihre Geschwister. Eine
Fähigkeit zu den Pipelines hinzuzufügen heißt deshalb nie, den Scheduler zu
ändern.
Fehlerbehandlung
Abschnitt betitelt „Fehlerbehandlung“Ein Schritt, der scheitert, speichert seine Fehlermeldung und wechselt auf
failed. Deklariert der Schritt continueOnFail, laufen die nachgelagerten
Schritte weiter und der Fehler liegt im Zustand des Laufs; sonst scheitert der
Lauf. Ein Schritt mit einer StepRetryPolicy wird zuerst wiederholt, mit
linearem oder exponentiellem Backoff, bevor eines von beidem greift.
Siehe auch
Abschnitt betitelt „Siehe auch“- Domain-Ereignisse: worauf ein Ereignis-Trigger hört
- Orchestrierung: ein Plan, den du freigibst und der in eine dieser Pipelines kompiliert wird
- Tickets und Delegation: was Pipeline-Schritte anlegen und abschließen
- Pipeline-Schrittarten: der vollständige Katalog von Arten, Bodies und Konfiguration
- Deine erste Pipeline bauen
- Eine Pipeline-Vorlage erstellen
- Eine Pipeline manuell ausführen
- Pipeline-Läufe überwachen
- Pipeline-Trigger einrichten