Template-Namensräume
Die Konfiguration eines Workflow-Knotens kann ${{ … }}-Tokens enthalten. Jedes Token benennt einen Namensraum und einen Pfad darin; die Engine setzt den referenzierten Wert ein, bevor der Knoten ausgeführt wird.
Namensräume sind isoliert: Jeder Resolver verarbeitet ausschließlich seine eigenen Tokens und lässt jeden anderen Namensraum Byte für Byte unverändert. Die Resolver können daher nacheinander über dieselbe Zeichenkette laufen, ohne sich gegenseitig zu stören.
Welche Namensräume ein Knoten auflöst, hängt von seiner Art ab – die Abdeckung ist nicht einheitlich, und die Matrix weiter unten ist maßgeblich.
Namensräume
Abschnitt betitelt „Namensräume“| Namensraum | Adressiert | Aufgelöst aus |
|---|---|---|
steps.<key>.<field> |
Das Ergebnis einer vorgelagerten Agent-Schritt-Aufgabe. <field> ist eines von branch, summary, pr_url, cost. |
tasks.git_branch sowie task_outcomes.summary / pr_url / cost_usd |
trigger.<field> |
Der Payload, der den Lauf gestartet hat. | Die trigger_context-Map des Laufs |
inputs.<name>[.<slot>] |
Die deklarierten Eingaben eines App-Laufs. Text-, Zahlen-, Auswahl- und Checkbox-Eingaben lösen direkt auf; eine file-Eingabe stellt die Slots path, url, bytes, original_filename, mime, size_bytes und sha256_hex bereit. |
trigger_context.inputs und die app_uploads-Zeilen des Laufs |
nodes.<key>.<path> |
Das Ergebnis-JSON eines beliebigen vorgelagerten Knotens – auch eines Nicht-Agent-Knotens. | Die result-Spalte des Kindlaufs |
resources.<name>.<field> |
Die nicht geheime Konfiguration einer Workspace-Ressource. | Die öffentliche Konfiguration der resources-Zeile |
secrets.<name> |
Ein Credential-Wert. | Spät aufgelöst, durch die Credential-Schicht – nie durch einen Notify-Text, einen Classify-Anhang oder eine Connector-Eingabe |
Ersetzungsmodi
Abschnitt betitelt „Ersetzungsmodi“Derselbe nodes.*-Namensraum ersetzt in einem von zwei Modi. Der Modus ist pro Konsument festgelegt – er ist keine Entscheidung pro Token.
| Modus | Gilt für | Verhalten |
|---|---|---|
| Text | notify_*-Felder, der Anhang-Ausdruck von llm_classify |
Ersetzt in eine Zeichenkette. Ein Nicht-String-Wert wird als sein JSON-Text dargestellt. Ein nicht auflösbares Token bleibt wörtlich stehen, damit eine Fehlkonfiguration sichtbar bleibt, statt die Nachricht stillschweigend zu leeren. |
| Typisiert | Die inputs eines connector-Knotens |
Ersetzt in einen JSON-Wertebaum. Ein nicht auflösbares Token lässt den Knoten fehlschlagen. |
Typisierte Ganzwert-Ersetzung
Abschnitt betitelt „Typisierte Ganzwert-Ersetzung“Im typisierten Modus wird eine Zeichenkette, die ausschließlich aus einem Token besteht, durch den referenzierten JSON-Wert selbst ersetzt – mit erhaltenem Typ:
# `rows` kommt als JSON-Array an, nicht als JSON-kodierte Zeichenkette.inputs: rows: "${{ nodes.collect.output.rows }}"Ein Token innerhalb einer längeren Zeichenkette wird weiterhin als Text interpoliert, denn das Ergebnis muss eine Zeichenkette bleiben:
# `label` kommt als Zeichenkette "batch 42 of run alpha" an.inputs: label: "batch ${{ nodes.collect.output.count }} of run ${{ trigger.name }}"Objekt-Schlüssel werden nie als Template behandelt – nur Werte, rekursiv durch Objekte und Arrays.
Pfade und Array-Indizierung
Abschnitt betitelt „Pfade und Array-Indizierung“nodes.<key>.<path> löst in zwei Schritten auf:
- Eine exakte Feld-Übereinstimmung auf dem Ergebnisobjekt gewinnt. Damit löst jede bereits bestehende einstufige Referenz weiterhin genau so auf wie zuvor – einschließlich eines Feldes, dessen Wert JSON-
nullist. - Andernfalls wird der Punktpfad in das Ergebnis hinein gelaufen. Ein Objektfeld steigt ab; ein Segment, das sich als Ganzzahl lesen lässt, adressiert einen Array-Index. Ein Objektfeld, das buchstäblich
0heißt, gewinnt weiterhin gegenüber der Array-Lesart.
inputs: first_row: "${{ nodes.query.output.rows.0 }}" city: "${{ nodes.query.output.rows.0.address.city }}"Ein Pfad, der nichts erreicht, ist nicht aufgelöst – unterscheidbar von einem Feld, das tatsächlich null enthält. Die Tiefe ändert nicht, was ein Wert bedeutet:
| Ergebnis | Textmodus | Typisierter Modus |
|---|---|---|
Pfad löst zu einem Wert auf (auch null) |
Dargestellt (null wird zu null) |
Eingesetzt |
| Pfad erreicht nichts | Token bleibt wörtlich stehen | Knoten schlägt fehl |
Segment-Obergrenze
Abschnitt betitelt „Segment-Obergrenze“Eine Referenz ist auf 32 Segmente begrenzt, gezählt über die gesamte logische Referenz – nodes, den Knotenschlüssel und jedes Pfadsegment. Die Obergrenze gilt gemeinsam mit Gate-Bedingungen, sodass ein Template und eine Bedingung für dieselbe Referenz dasselbe Budget erhalten. Eine Referenz über der Grenze löst nicht auf.
Nur nodes.* hat mehrstufige Pfade:
| Namensraum | Pfadtiefe |
|---|---|
steps.* |
1 – ein fester Satz von vier Feldern |
trigger.* |
1 – eine flache Suche im Trigger-Kontext |
inputs.* |
1, plus ein fester Datei-Slot-Suffix |
nodes.* |
N – erst exaktes Feld, dann der Punktpfad-Lauf |
Namensraum-Abdeckung nach Knotenart
Abschnitt betitelt „Namensraum-Abdeckung nach Knotenart“An jedem Knotenmodul gemessen. Die Asymmetrien sind real und tragend.
| Knotenart / getemplateter Slot | steps.* |
trigger.* |
inputs.* |
nodes.* |
resources.* |
|---|---|---|---|---|---|
agent-Prompt |
ja | ja | ja | nein | nein |
agent-Prompt innerhalb eines loop-Körpers |
ja | ja | nein | nein | nein |
human-Prompt |
ja | ja | nein | nein | nein |
loop-over-Ausdruck |
ja | ja | nein | nein | nein |
notify_*-Felder |
ja | ja | nein | ja (Text) | ja |
llm_classify-Anhang-Ausdruck |
ja | ja | ja | ja (Text) | ja |
code-Quelltext |
ja | ja | ja | nein | nein |
connector-inputs |
ja | ja | ja | ja (typisiert) | nein |
| jede andere Art | nein | nein | nein | nein | nein |
„Jede andere Art“ – gate, end, transform, db_query, db_execute, http_request, imap_ack, wait_event, app – löst keinen ${{ }}-Namensraum auf.
Zwei Asymmetrien werden leicht übersehen:
- Ein
agent-Prompt löstinputs.*auf oberster Ebene auf, nicht aber innerhalb einesloop-Körpers. - Der Payload von
llm_classifywird überhaupt nicht getemplatet. Nur sein Anhang-Ausdruck.
Größen-Obergrenzen
Abschnitt betitelt „Größen-Obergrenzen“Es gibt zwei Obergrenzen. Sie regeln unterschiedliche Pfade und sind bewusst nicht dieselbe Zahl.
| Obergrenze | Regelt | Bei Überschreitung |
|---|---|---|
| 8 MiB | Den Payload eines connector-Knotens – die aufgelösten inputs, die er erhält, und alles, was er in sein Laufergebnis schreibt: die Ausgabe eines Erfolgs, die Fehlermeldung eines Fehlschlags |
Der Knoten schlägt laut fehl und nennt den Knotenschlüssel und die tatsächliche Größe. Nie gekürzt. |
| 1 MiB | Den Inline-Bytes-Zweig des Datei-Slot-Namensraums, ${{ inputs.<name>.bytes }} |
Der bytes-Slot wird übersprungen; das Token bleibt wörtlich stehen, damit ein nachgelagerter Knoten es sichtbar machen kann |
Die Connector-Obergrenze ist absichtlich symmetrisch. Der Payload eines Connectors wird in die result-Spalte seines Kindlaufs geschrieben, und genau diese Spalte liest der nodes.*-Namensraum. Ein ungedeckelter Payload würde daher einen Knoten später zur ungedeckelten typisierten Eingabe, und der Eingabewächter würde gegen den konsumierenden Knoten auslösen – für einen Payload, den der erzeugende Knoten angelegt hat. Eine Konstante deckelt beide Richtungen, sodass der Knoten, der einen zu großen Payload erzeugt hat, auch der Knoten ist, der fehlschlägt.
Das gilt auch für einen fehlgeschlagenen Lauf. Ein Fehlschlag verwirft die Ausgabe, schreibt aber die Fehlermeldung des Gasts in dasselbe result, wo ${{ nodes.<key>.error }} sie genauso liest wie .output – die Fehlermeldung wird deshalb an derselben Obergrenze gemessen, und eine zu große Meldung lässt den Knoten fehlschlagen, der sie erzeugt hat, statt weiterzureisen.
Der Eingabewächter läuft nach der typisierten Auflösung und misst damit, was der Connector wirklich erhalten würde – einschließlich der durch Ersetzung eingefügten Ganzwerte.
Große Payloads per Referenz
Abschnitt betitelt „Große Payloads per Referenz“Die typisierte Ersetzung fügt einen ganzen Wert ein, sodass eine einzelne Referenz das vollständige Ergebnis eines vorherigen Knotens tragen kann. Oberhalb der Obergrenze reist der Payload gar nicht mehr inline.
Das unterstützte Muster ist, einen Namen plus einen Pfad zu übergeben und den Connector die Bytes aus seiner eigenen gebundenen Ressource streamen zu lassen – über die Egress-Autorität, die diese Ressource ohnehin freischaltet:
# Statt die Dokument-Bytes einzubetten, reist eine Referenz darauf.inputs: documents: - name: "invoice-2026-08.pdf" path: "/exports/2026/08/invoice-2026-08.pdf"Prüfung beim Speichern
Abschnitt betitelt „Prüfung beim Speichern“Die nodes.*-Referenzen eines connector-Knotens werden beim Speichern, Installieren oder Seeden geprüft – nicht beim ersten Lauf. Die vollständige Spezifikation liegt in diesem Moment vor, eine kaputte Referenz ist also genau dort entscheidbar.
| Abgelehnt | Grund |
|---|---|
| Der referenzierte Knotenschlüssel existiert nicht im Workflow | Ein Tippfehler würde sonst erst Monate später beim ersten echten Lauf auffallen |
| Der Knoten referenziert sein eigenes Ergebnis | Sein Ergebnis existiert nicht, während er läuft |
| Der referenzierte Knoten ist nicht vorgelagert im DAG | Sein Ergebnis existiert noch nicht, wenn dieser Knoten läuft |
Vorgelagert heißt: rückwärts erreichbar über die Kanten des Graphen plus die Enthaltensein-Kante des Schleifenkörpers – ein in einem loop verschachtelter Knoten sieht berechtigterweise alles, was sein übergeordneter Rahmen sieht.
Das Connector-Eigenschaftenfeld im Workflow-Builder wertet dieselben drei Regeln bereits während der Eingabe aus und benennt die vorgelagerten Knotenschlüssel, die vom ausgewählten Knoten aus tatsächlich referenzierbar sind.