Components
TL;DR
- Ein component ist die Einheit der Fähigkeit der Plattform: ein typisiertes, versioniertes, containerisiertes Stück Code, das eine Aufgabe erledigt — ein Bild lesen, einen Detector ausführen, einen Audio-Frame normalisieren, in eine Queue schreiben. Es ist über viele backends wiederverwendbar und gehört einem Team.
- Ein component deklariert, was es konsumiert, was es produziert und wie es konfiguriert werden kann — typisierte input streams, typisierte output streams, typisierte Parameter, optionale file-Slots. Diese Deklaration ist der Vertrag, den jede andere Schicht der Plattform liest.
- Components haben unveränderliche releases. Jedes Publish erzeugt eine prerelease; promote verwandelt eine prerelease in eine released version. Backends pinnen bestimmte releases, sodass promote kein laufendes deployment ändert.
- Ein component besitzt Verhalten. Ein backend besitzt Komposition. Ein deployment besitzt runtime. Die drei Primitive getrennt zu halten ist das, was eine einzelne Fähigkeit viele Produkte antreiben lässt, ohne den Code zu forken.
- Die meisten components sind stateless Funktionen: Input rein, Output raus. State, virtual streams und custom Pull-Policies sind fortgeschrittene Themen für die Fälle, die sie brauchen; die Grundform deckt die meisten echten components ab.
Was ein component tatsächlich ist
Ein component ist die kleinste wiederverwendbare Einheit von Fähigkeit, die die Plattform modelliert. Konkret ist es eine typisierte Funktion, verpackt als containerisierter worker, mit einem Manifest, das deklariert, was es konsumiert, was es produziert und wie es konfiguriert werden kann. Diese Deklaration — die component.yml — ist der Vertrag, den jede andere Schicht liest: backends nutzen ihn, um die Graph-Verdrahtung vor dem Deploy typzuprüfen, der Katalog nutzt ihn für die Discovery, das runtime nutzt ihn, um zu bestimmen, was der Container des worker erwartet.
Die Granularität ist beabsichtigt. Eine bloße Funktion ist zu klein, um unabhängig zu operieren: kein Manifest, kein Container, keine Versionierung. Eine ganze Pipeline ist zu groß, um über Produkte hinweg wiederverwendet zu werden. Ein component sitzt auf der Ebene, auf der eine Aufgabe auf eine Einheit abbildet, die gepinnt, validiert, wiederverwendet, getauscht und für sich operiert werden kann. Der typisierte Vertrag und das unveränderliche release-Modell sind das, was diese Einheit komponierbar macht statt nur isoliert.
Der Standardvertrag ist, dass components an der Graph-Grenze stateless sind. Die Funktion konsumiert typisierte Inputs und produziert typisierte Outputs; alles andere — Model-Weights, Konfiguration, optionaler state, Side-Channel-Events — ist Maschinerie, die das component deklariert, wenn es sie braucht. Dieser Default hält die meisten components klein und lässt den Scheduler sie frei parallelisieren. Die Opt-ins existieren für die Fälle, die sie brauchen, und jedes davon schränkt ein, wie das runtime die vertex schedulen kann, was der richtige Druck ist, um components einfach zu halten.
Mentales Modell
┌──── component release (immutable) ─────┐
│ │
│ component.yml src/ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ language │ │ Python or │ │
│ │ inputs[] │ │ C++ code │ │
│ │ outputs[] │ │ │ │
│ │ config │ │ implements │ │
│ │ files │ │ the typed │ │
│ │ tags │ │ function │ │
│ └─────────────┘ └─────────────┘ │
│ │
└────────────────────┬───────────────────┘
│ pinned by
▼
backend vertex
Die release ist das unveränderliche Artefakt. Ein backend pinnt sie als vertex, bindet ihre Parameter und files und verdrahtet ihre Streams. Dieselbe release kann als vertex in vielen backends sitzen; jedes backend trägt seine eigenen Bindungen und seinen eigenen Graph-Kontext. Promote zeichnet eine neue released-Zeile auf; das Pinnen ist das, was ein backend davon isoliert — ein backend, das release_v3 gepinnt hat, läuft weiter v3, nachdem v4 default geworden ist.
Siehe Backends für den Graphen, in den sich die release einklinkt, und Types für den typisierten-Stream-Vertrag, den ihre Inputs und Outputs erfüllen.
Was dir "typisierte Funktion" tatsächlich bringt
Verwende diesen Rahmen das erste Mal, wenn du fragst "was garantiert der component-Vertrag".
Der deklarierte typisierte Vertrag ist im Manifest klein — ein paar Zeilen pro Stream — und überall sonst tragend. Die Typ-Inferenz des backend nutzt ihn, um abzulehnen, einen Image-Output in einen [BoundingBox]-Input zu verdrahten. Die CLI nutzt ihn, um sinnvolle Fehlermeldungen zu rendern, wenn ein Parameter fehlt. Der Katalog nutzt ihn, um components nach den Modalitäten zu filtern, die sie verarbeiten. Das runtime nutzt ihn, um zu bestimmen, welche Container-Ressourcen das component beim Start braucht.
Der component-Autor schreibt den Vertrag einmal; jede andere Partei — Graph-Autoren, nachgelagerte Konsumenten, Ops-Teams, spätere Maintainer — liest ihn. Das Deklarieren des Vertrags verschiebt Verdrahtungs- und Parameterfehler zur Bearbeitungszeit, wo die Typ-Inferenz des backend sie fängt, statt sie auf einen fehlgeschlagenen Deploy zu vertagen. Das ist der praktische Grund, warum das Manifest verpflichtend statt optional ist.
Die typisierten Werte selbst stammen aus einer von drei Quellen: pipelang-atomic types (Double, String, Int64), composite types, aus ihnen gebaut ([BoundingBox], Maybe<String>, (Image, [Double])), oder named types aus der Type-Registry der Plattform (Image, Tensor, AudioFrame, BoundingBox). Die named types tragen Domänensemantik: ein Image ist nicht nur ein Tensor von Bytes, es ist der typisierte Wert, auf den sich jedes bildverarbeitende component einigt, mit Shape, Channel-Order und Metadaten, die nachgelagerte Konsumenten direkt lesen statt neu abzuleiten.
Siehe Named types für die Registry, die Image, Tensor und den Rest stützt.
Stateless als Default, stateful wenn das Verhalten Historie braucht
Verwende den Default; greife nur dann nach state, wenn der nächste Output wirklich von den vorherigen abhängt.
Das Standard-component ist eine stateless Funktion: das runtime kann sie für dieselbe vertex viele Male nebenläufig aufrufen, weil kein Aufruf von einem anderen abhängt. Das lässt den Scheduler eine einzelne vertex unter Last über CPU-Kerne fan-outen oder unabhängige Invocations parallel laufen, ohne Ordering zu koordinieren.
Das Deklarieren von state ändert diesen Vertrag. Das component bittet die Plattform, Memory zwischen Aufrufen zu persistieren und die Funktion pro vertex eine zur Zeit aufzurufen, sodass der next state des vorherigen Aufrufs für den nächsten sichtbar ist. Die vertex wird history-abhängig: Tracker, gleitende Windows, Session-Maps, Conversation-Memory, Debouncing und "emittiere erst nach N Sekunden gepufferten Audios" brauchen alle state, um korrekt zu funktionieren. Eine stateful vertex ist seriell pro Replica, was der richtige Druck ist, um state aus components herauszuhalten, die ihn nicht brauchen.
Was außerhalb des persistierten state bleibt, ist die per-process-Maschinerie: Model-Handles, Datenbank-Clients, kompilierte Regexes, Caches, die beim Start regeneriert werden können. Diese gehören in Module-Globals oder Setup-Hooks, nicht in den persistierten state der Plattform. Persistierter state ist für Verhaltens-Memory — die Werte, deren Verlust zwischen Aufrufen verändern würde, was der nächste Aufruf emittiert. Alles andere ist process-lokal und lebt entsprechend.
Side-Effects leben hinter virtual streams
Verwende dies, wenn das component mit der Welt außerhalb des Graphen sprechen muss: HTTP, WebSocket, Devices, SDK-Callbacks, file-Writes mit Retry-Semantik.
Die meisten components konsumieren aus Graph-Inputs, führen eine deterministische Funktion aus und emittieren an Graph-Outputs. Manche components haben eine schwierigere Aufgabe: sie besitzen einen HTTP-Listener, der Traffic asynchron empfängt, einen Kamera-Thread, der Frames unabhängig vom Graph-Timing erzeugt, ein LLM-SDK, das Tokens durch einen Callback zurückstreamt, einen file-Writer, der Retry-Handling braucht. Diese Side-Effects direkt in die graph-zugewandte Funktion einzufädeln macht es dem Scheduler unmöglich zu bestimmen, wann die Funktion sicher aufzurufen ist.
Virtual streams isolieren diese Arbeit. Virtual input ist eine component-lokale Queue, befüllt von Code außerhalb des normalen graph-zugewandten Ticks: einem HTTP-Listener, einem Device-Thread, einem Streaming-SDK-Callback. Das component kann virtual input in seinen pull request aufnehmen, sodass diese externen Events weiterhin durch dieselbe scheduler-sichtbare Funktion wie normale Stream-Nachrichten fließen. Virtual output ist das Inverse: eine Queue von Side-Effect-Arbeitselementen, die das component aus einem Tick zurückliefert, die ein bridge-Thread asynchron ausführt und Ergebnisse zurück durch virtual input speisen kann.
Die Disziplin, die virtual streams funktionieren lässt, ist, dass die graph-zugewandte Funktion scheduler-sichtbar bleibt. Side-Effects leben hinter den Queues; die Funktion liest und schreibt sie; die bridge führt die IO aus. Diese Trennung ist das, was ein component mit der Außenwelt sprechen lässt, während der Scheduler sein Modell behält, wann die Funktion läuft.
Releases sind unveränderlich; promote ist bewusst
Verwende dieses mentale Modell, wann immer du darüber nachdenkst, "warum hat mein backend den neuen Code nicht aufgenommen".
Jedes Publish erzeugt eine prerelease: einen privaten validierten Build, den der Aufrufer in Test-backends deployen kann. Promote ist der separate, bewusste Schritt, der eine prerelease in eine released version verwandelt: das Image geht in die Registry, die in component.yml deklarierten Tags bewegen sich (latest, default), und die version wird für jeden im Audience-Bereich des workspace deploybar. Die version-ID ändert sich nicht; nur ihr Zustand.
Backends pinnen bestimmte release-IDs. Das Promoten einer prerelease ändert die gepinnte version keines deployten backend — das backend läuft weiter, was auch immer es zum Zeitpunkt des letzten Deploy gepinnt hat. Ein backend auf eine neuere release vorwärtszurollen ist eine explizite vertex-Mutation, gefolgt von einem Redeploy. Diese Trennung ist der Grund, warum das Promoten einer kaputten version für sich allein kein laufendes backend lahmlegen kann. Das laufende backend ist gepinnt; die neue release ist ein neues adressierbares Artefakt im Katalog.
Siehe Publish-Semantik für die volle publish + promote-Schleife.
Was ein component besitzt und was nicht
Ein component besitzt seine Identität (Display-Name plus workspace-skopierten Slug), seine Sprache (Python oder C++), seine deklarierten input- und output-Stream-Typen, sein typisiertes config_schema, sein optionales file_schema für Model- und Daten-Slots, sein optionales generated_file_schema für Outputs, die es als workspace-files zurückschreibt, und seinen Implementierungs-Quellcode unter src/. Es besitzt auch seine Discovery-Metadaten — die Katalog-Kategorie und Modalitäts-Tags, die README und AGENT.yml, die mit der release reisen.
Es besitzt nichts Graph-Förmiges. Das backend, in dem es auftritt, die vertex-Bindungen seiner Parameter in diesem backend, die file-IDs, die in seine Slots gebunden sind, die endpoint-Aliase, durch die seine Outputs offengelegt werden, das deployment, das es ausführt, das runtime, das das deployment hostet — all das sind backend-und-deployment-Anliegen. Das component ist die Fähigkeit; alles andere ist Komposition und runtime.
Diese Trennung ist der ganze Grund, warum ein einzelnes component viele Produkte antreiben kann. Ein Detector-component ist dasselbe in einem Lagerhaus-Sicherheits-backend, einem Retail-Traffic-backend und einem klinischen Bildgebungs-backend; jedes backend verdrahtet es anders, bindet andere Parameter, deployt es auf anderem Compute. Das component muss nichts davon wissen.
Die Qualitätslatte
Ein gültiges component typprüft; ein gutes component ist allein aus seinem Manifest heraus vertrauenswürdig. Die Latte, komprimiert: Vertrag vor Code entworfen und Beschreibungen exakt zutreffend; ein Output auf jedem Port für jeden gültigen Input, Abwesenheit immer explizit; Registry-carrier-Typen über das SDK gebaut, niemals von Hand gepackt; oneof-Outputs als Projektionen eines kanonischen Ergebnisses implementiert, mit dem einmal beim Start aufgelösten Arm; Parameter in den Typ hinein verfeinert (String<"fast" | "accurate">, maybe-typisierte secrets, echte file-Layouts); Abhängigkeiten exakt gepinnt; Invarianten an der Grenze per assert geprüft statt in catch-and-continue eingewickelt; Tests, die jede Input-Form und jeden Output-Arm mit echten Assertions abdecken; und Dokumentation, die für Menschen wie für Agents geschrieben ist und dem Quellcode entspricht. Components, die diese Latte erfüllen, können von Menschen — und Agents — verdrahtet, getauscht und betrieben werden, die die Implementierung nie gelesen haben.
Wo das hinpasst
Components sind das tragende Primitive der Plattform für was Code tut. Backends komponieren sie; deployments führen sie aus; der Katalog entdeckt sie; das Typsystem schränkt ein, wie sie zusammen verdrahtet werden; das release-Modell hält ihr Verhalten über die Zeit reproduzierbar. Jedes andere Konzept auf der Plattform existiert, um components wiederverwendbar, komponierbar, betreibbar oder auffindbar zu machen, sodass eine einzelne Fähigkeit viele Produkte bedient, statt als maßgeschneiderter Glue-Code für jedes neu implementiert zu werden.
Die Disziplin, die die Plattform von component-Autoren verlangt, ist klein: deklariere den typisierten Vertrag genau, defaulte auf stateless, greife nur dann nach state und virtual streams, wenn die Grundform den Vertrag nicht ausdrücken kann, und behandle releases als unveränderlich. Im Gegenzug bietet die Plattform Graph-Typ-Prüfung, Katalog-Discovery, runtime-Scheduling, deployment, Observability und Beweisschleifen. Der Autor schreibt die Funktion; alles darum herum ist die Verantwortung der Plattform.
Verwandt
- Backends — released components in einen typisierten Graphen verdrahten.
- Types — der Vertrag, der schlechte Verdrahtung vor dem runtime fängt.
- Named types — die Registry hinter
Image,Tensor,AudioFrame. - Publish-Semantik — die publish- und promote-Schleife im Detail.
- File schema — die files deklarieren, die ein component konsumiert und produziert.
- Build systems — wie die Plattform Quellcode in ein validiertes Image verwandelt.
- Solutions — component + backend + deployment + surface, End-to-End.
- Quickstart — der Routen-Wähler für deine erste Aufgabe.