Eine einzelne AI-Operation zu erstellen ist unkompliziert: Eingabe nehmen, ein LLM aufrufen, Ausgabe zurückgeben. Eine mehrstufige Workflow-Engine zu bauen, die Verzweigungen, Schleifen, parallele Ausführung, menschliche Genehmigungsgates und automatische Retries unterstützt, ist ein völlig anderes Problem.
Dieser Beitrag behandelt die Architektur von JieGous Workflow-Engine — das Ausführungsmodell, die 8 Schritttypen, wie Daten zwischen Schritten fließen, wie Genehmigungen die Ausführung pausieren und fortsetzen, und die Leitplanken, die alles zuverlässig halten.
Ausführungsmodell
Ein Workflow ist ein gerichteter Graph von Schritten. Die Ausführung beginnt mit executeWorkflow(), das einen WorkflowRun-Datensatz erstellt, einen geteilten StepExecutionContext aufbaut und executeStepList() aufruft, um Schritte sequenziell zu verarbeiten.
Der Kontext trägt eine previousStepOutputs-Map — ein Key-Value-Speicher, in dem jeder abgeschlossene Schritt seine Ausgabe für nachfolgende Schritte ablegt. Dies ist das Rückgrat des Datenflusses. Schritt B kann die Ausgabe von Schritt A mittels Template-Syntax wie {{step.stepA.fieldName}} referenzieren.
Jeder Workflow hat ein konfigurierbares Timeout (Standard 5 Minuten), durchgesetzt über ein deadlineMs-Feld im Kontext. Einzelne Schritte haben ebenfalls ihr eigenes Timeout (Standard 60 Sekunden), durchgesetzt durch einen withStepTimeout()-Wrapper.
Die 8 Schritttypen
Recipe Step
Das Arbeitstier. Führt ein wiederverwendbares Prompt-Template über executeRecipe() aus und speichert die geparste Ausgabe in previousStepOutputs. Input-Mapping löst Referenzen zu Workflow-Eingaben, vorherigen Schrittausgaben, statischen Werten oder Schleifenelementen auf.
Condition Step
Wertet einen booleschen Ausdruck aus und führt entweder thenSteps oder elseSteps rekursiv aus. Dies ist eine echte Verzweigung — beide Pfade können jeden Schritttyp enthalten, einschließlich verschachtelter Bedingungen. Die Engine ruft executeStepList() rekursiv auf dem gewählten Zweig auf.
Loop Step
Iteriert über eine Kollektion und führt eine Liste von Unterschritten für jedes Element aus. Die Kollektion kann aus 4 Quellen stammen: einem statischen Array, das im Workflow definiert ist, der Ausgabe eines vorherigen Schritts, einem Workflow-Eingabefeld oder übergeordneten Schleifenelementen.
Jede Iteration erhält ihre eigene loopContext-Map, sodass Unterschritte das aktuelle Element über {{loop_item.fieldName}} referenzieren können. Iterationsergebnisse werden als Array mit verschachtelten stepRuns für Observability gespeichert.
Parallel Step
Führt mehrere Zweige gleichzeitig über Promise.allSettled() aus. Jeder Zweig ist eine unabhängige Liste von Schritten mit eigenem stepRuns-Array. Dies ist nützlich, wenn mehrere unabhängige Operationen gleichzeitig laufen können — zum Beispiel einen Lead gleichzeitig aus drei verschiedenen Datenquellen anreichern.
Approval Step
Der architektonisch interessanteste Schritttyp. Wenn die Ausführung einen Genehmigungsschritt erreicht, wirft er einen ApprovalPauseError. Dies ist eine kontrollierte Exception — kein Absturz.
Der Fehler wird auf der obersten Ebene gefangen, der WorkflowRun wird mit dem Status pending_approval persistiert, und berechtigte Genehmiger werden per E-Mail benachrichtigt. Die Ausführung stoppt vollständig. Keine Ressourcen werden gehalten.
Wenn ein Genehmiger handelt (genehmigen oder ablehnen über die API), lädt resumeWorkflowFromApproval() den persistierten Run, ruft reconstructPreviousOutputs() auf, um die previousStepOutputs-Map aus den gespeicherten stepRuns wiederherzustellen, und setzt die Ausführung ab dem nächsten Schritt nach dem Genehmigungsgate fort.
Die Rekonstruktion ist rekursiv — sie durchläuft thenStepRuns, elseStepRuns, iterations[] und branchStepRuns[], um jede verschachtelte Ausgabe wiederherzustellen. Das bedeutet, Genehmigungen funktionieren korrekt, selbst wenn sie sich innerhalb eines bedingten Zweigs oder einer Schleifenitertion befinden.
Write-to-KB Step
Erfasst die Ausgabe eines Schritts und schreibt sie in ein Knowledge-Base-Dokument. Dies ermöglicht Workflows, die institutionelles Wissen aufbauen — ein Support-Triage-Workflow könnte Lösungszusammenfassungen in eine KB schreiben, die zukünftige Ausführungen über RAG referenzieren.
Handoff Step
Benachrichtigt Benutzer in einer Zielabteilung per E-Mail und In-App-Benachrichtigung. Es ist ein No-Op für die Ausführung — es produziert keine Ausgabe und blockiert die Pipeline nicht. Nützlich für Eskalationsflüsse, bei denen ein Mensch alarmiert werden muss, aber der Workflow fortgesetzt werden sollte.
Browser Action Step
Führt einen MCP-Tool-Aufruf über die Browser-Extension aus. Bezieht einen Client aus dem MCP-Verbindungspool, löst Template-Argumente (Workflow-Eingabefelder und vorherige Schrittausgaben) auf und gibt das Tool-Ergebnis zurück.
Datenfluss: Template-Auflösung
Schritte referenzieren sich gegenseitig durch eine Template-Syntax, die zur Ausführungszeit aufgelöst wird:
{{workflow_input.fieldName}}— Referenziert die Eingabedaten des Workflows{{step.stepId.path.to.value}}— Referenziert die Ausgabe eines vorherigen Schritts mittels Punkt-Notation{{loop_item.fieldName}}— Referenziert das aktuelle Element in einer Schleifeniteration
Der Resolver verwendet getNestedValue() für Punkt-Notations-Pfadnavigation und handhabt Arrays und verschachtelte Objekte. Input-Mappings deklarieren ihre Quelle explizit: workflowInput, previousStep, static oder loopItem.
Retry und Fehlerbehandlung
Nicht alle Fehler sind permanent. Rate Limits, transiente 5xx-Fehler, Timeouts und Verbindungsfehler sind retry-fähig. Clientseitige Validierungsfehler (4xx) nicht.
Die Retry-Strategie verwendet exponentielles Backoff mit Jitter: Math.min(30000, 2000 * 2^attempt + random jitter). Das sind ungefähr 2 Sekunden, 4 Sekunden, 8 Sekunden, 16 Sekunden, gedeckelt bei 30 Sekunden, mit zufälligem Jitter, um Thundering-Herd-Probleme zu vermeiden. Standard-Maximalversuche ist 3, konfigurierbar pro Schritt.
Jeder Retry wird mit dem Versuchsindex und Fehlerdetails geloggt. Wenn alle Versuche fehlschlagen, wird der Schritt als fehlgeschlagen markiert und der Workflow wird beendet (sofern die Fehlerbehandlung auf Workflow-Ebene nichts anderes spezifiziert).
Concurrency-Kontrolle
Jedes Konto ist auf 10 gleichzeitige LLM-Aufrufe über ein Redis-Semaphor begrenzt. Dies verhindert, dass ein Batch-Workflow Provider-Verbindungen monopolisiert.
MCP-Verbindungen verwenden einen gepoolten Client mit LRU-Eviction (maximal 20 Verbindungen, 60 Sekunden Idle-Timeout). Der Pool handhabt Verbindungsaufbau, Keepalive und Bereinigung.
Beide Systeme verwenden Fail-Open-Semantik bei Redis-Fehlern — eine degradierte Cache-Schicht sollte niemals die Workflow-Ausführung verhindern.
Observability
Drei Schichten der Observability laufen gleichzeitig:
Prometheus-Metriken tracken workflowExecutionsTotal und workflowDuration nach Status. Dies liefert operative Dashboards für Erfolgsraten und Latenz-Perzentile.
OpenTelemetry-Spans erstellen eine Trace-Hierarchie mit Workflow- und Schritt-Level-Spans, die Workflow-ID, Name und Schritt-Metadaten tragen. Diese integrieren sich mit jedem OTel-kompatiblen Backend.
Execution Traces sind ein benutzerdefinierter hierarchischer Span-Baum, der über Fire-and-Forget nach Firestore persistiert wird. Diese treiben den detaillierten Run-Inspector in der UI an und zeigen genau, was bei jedem Schritt passiert ist, einschließlich Eingaben, Ausgaben, Timing und Retry-Versuche.
Pre-Flight-Checks
Vor Beginn der Ausführung führt die Engine eine Validierung durch:
- Eingabeschema-Validierung — Workflow-Eingaben werden gegen das deklarierte Schema geprüft
- Mapping-Validierung — Alle Schritt-Input-Mappings werden verifiziert (referenzierte Schritte existieren, Pfade sind plausibel)
- MCP-Verifikation — Wenn Schritte Browser-Aktionen erfordern, verifiziert die Engine die MCP-Client-Konnektivität vorab, anstatt mitten in der Ausführung zu scheitern
- Auto-Kontext-Auflösung — Knowledge-Base-Dokumente werden aus Workflow-, Abteilungs- und expliziten ID-Quellen aufgelöst, sodass der RAG-Kontext bereit ist, bevor der erste Schritt läuft
Erkenntnisse
Genehmigungsgates als Exceptions vereinfachen die Architektur. Unser erster Entwurf verwendete eine State Machine mit expliziten Pause/Resume-Zuständen. Der Exception-basierte Ansatz ist sauberer — der Workflow läuft vorwärts, bis er auf eine Genehmigung trifft, wirft, persistiert und stoppt. Resume rekonstruiert den Zustand und fährt fort. Keine komplexen Zustandsübergänge zu verwalten.
Rekursive Ausgabe-Rekonstruktion ist die Komplexität wert. Die Wiederherstellung von previousStepOutputs aus persistierten Step Runs erfordert das Durchlaufen jeder Verschachtelungsebene — Bedingungen, Schleifen, parallele Zweige. Es ist komplexer Code, aber er bedeutet, dass Genehmigungen auf jeder Verschachtelungstiefe korrekt funktionieren, ohne Spezialbehandlung.
Per-Step-Timeouts verhindern kaskadierende Verzögerungen. Ein einzelner langsamer LLM-Aufruf sollte nicht das gesamte Workflow-Timeout aufbrauchen. Der 60-Sekunden-Standard pro Schritt fängt hängende Anfragen früh ab, und das Workflow-Level-Timeout fungiert als Sicherheitsnetz.
Fire-and-Forget-Observability hält den Hot Path schnell. Execution Traces, Webhook-Zustellungen und Zielausgaben werden alle asynchron nach Abschluss der Hauptausführung dispatcht. Der Benutzer erhält sein Ergebnis, ohne auf das Beenden der Observability-Schreibvorgänge warten zu müssen.