Backends

TL;DR

  • A backend is the composition layer of the platform: a typed graph of component releases with their parameters and files bound, ready to deploy. Components define what code does; backends define what a specific product workflow does with that code.
  • A backend is an object, not a script: it can be inspected, edited, undone, redeployed, and queried for the exact component release in production. The platform owns the mutation surface; see Backend operations for how every change is recorded and reversed.
  • The platform type-checks the graph at edit time, not just at deploy. Connecting an Image output into a [BoundingBox] input is rejected immediately, with a precise error that names the edge, rather than surfacing later as a runtime failure inside a container.
  • Backends pin specific component release versions, not floating tags. Promoting a new component release does not silently change any backend that was already wired against an older release.
  • A backend is not running code. It is the checked definition of a product workflow. Running it is the job of a deployment; the backend itself stays the inspectable, undoable, redeployable graph definition.

What a backend actually is

A backend is how Pipelogic represents one product's workflow: a typed, checked, reproducible composition of component capabilities. Components define what code does; a backend defines what a specific product does with that code, and it does so as an explicit, inspectable object rather than as orchestration code wrapped around the components.

Modelling composition as a first-class object is what makes the workflow itself operable. Because the backend is an object and not a script, it can be inspected by a teammate, edited by a tool, undone after a bad change, redeployed without rebuilding, and queried for which component release version is actually in production. The shape of the system is the graph, not glue code spread across files and the author's memory, and the type guarantees the components declare are carried through the wiring rather than lost when ports are connected by hand.

A backend is a graph: vertices are pinned component release versions plus the parameters and files bound to each one; edges connect typed output streams to compatible typed input streams; the whole structure is reproducible because every change to it is a recorded operation in the backend's log. The backend can be inspected, edited, undone, compacted, type-checked, deployed, redeployed, forked, and reasoned about, because it is a first-class object rather than a side-effect of code.

The separation is deliberate: composition lives in the backend, not in the component. A component is the capability; the backend is the use of that capability in one specific workflow. A single component can sit as a vertex in many backends, each one wiring it to different upstreams, parametrising it differently, binding different files. That is why building one good detector once can serve many products instead of being forked per product. This is the standard modularity payoff of microservices — independently versioned, reusable capabilities — without the usual tax: no per-service glue code, no orchestration layer to operate, no contract drift, because the wiring is a typed graph the platform checks instead of code each team maintains by hand.

Mental model

   ┌──────────────────────────────────────┐
   │ input_image_http                     │
   │ ()  →  (Image, String)               │
   └──────────────────────────────────────┘
     vertex 1
           │  out 0 → Image

   ┌──────────────────────────────────────┐
   │ convert_image_format                 │
   │ Image  →  Image                      │
   │ color_model=RGB                      │
   └──────────────────────────────────────┘
     vertex 2
           │  out 0 → Image

   ┌──────────────────────────────────────┐
   │ detect_objects_ultralytics_yolo      │
   │ Image  →  [BoundingBox]              │
   │ threshold=0.4 · model=<file>         │
   └──────────────────────────────────────┘
     vertex 3

Each vertex is a pinned release version plus its bindings — two vertices on the same release with different parameters or different file bindings are different vertices. Each edge is (from-vertex, from-output) → (to-vertex, to-input): inputs accept at most one upstream edge, outputs can fan out to multiple consumers. The whole structure is what gets deployed; nothing about a deployment is implicit.

See Components for the unit a vertex pins, and Types for the contract the edges satisfy.

The graph is not stored as a file you open and edit; the platform owns the mutation surface, every change is a recorded operation, and the current graph is derived by replaying that log. See Backend operations for the operation vocabulary, how each change is reversed, and the compact semantics.

The type system catches wiring at edit time

Use this framing to understand why a connect operation is rejected.

Every component declares the type of each of its streams in its component.yml. When a backend wires two vertices, the platform's type inference runs across the proposed edge, and across the whole graph, and determines whether the connection is sound. An Image output into a [BoundingBox] input is rejected, with an error that names the edge and the conflict. A Maybe<String> into a String is rejected (the wrapping is significant). A generic [t] into [Image] is accepted only if t instantiates as Image consistently elsewhere in the graph.

The check runs at edit time, not only at deploy. That is the load-bearing detail. Validating at the moment of the edit means a type mismatch is reported on the operation that caused it, not as a runtime crash inside a deployed container. When a connection is unsound the operation is not recorded, the graph stays valid, and you correct the edit and continue.

The same check re-runs at deploy time, and it is deliberately the same machine — the deploy-time check is not stricter than the edit-time check. An edit the platform accepts is accepted again at deploy. That symmetry is what lets backend authors rely on the edit-time feedback.

See Types for the type system the check runs against.

Mutable bindings versus topology changes

Use this framing whenever you wonder whether a backend edit needs a redeploy.

Most edits to a backend graph require a redeploy: adding a vertex, removing a vertex, changing the pinned release version of a vertex, connecting or disconnecting edges, binding a different file, or changing a parameter that is not declared mutable: true. Those are topology changes — the running containers no longer match the new graph, so they are replaced.

A small set of edits do not require a redeploy. Parameters declared mutable: true in the component's config_schema update against the running deployment: the platform pushes the new value into the running container, and the component reads it through its subscribe-config path on the next invocation. This applies to thresholds, runtime knobs, and any parameter the component author has marked safe to tune live without redeploying.

Topology changes have no mutable equivalent. There is no live add of a vertex and no live rewire of an edge; those changes redefine the graph, and the runtime is redeployed to match. The same applies to file bindings — the runtime mounts files at container start, so rebinding requires a fresh container.

The design enforces that mutable parameters are declared by the component, not by the backend. The component author decides which parameters are runtime-safe; the backend operator gets exactly that surface for live tuning and nothing more. That separation keeps parameters that are unsafe to change live from being treated as live-editable.

What forking a backend means

Use this whenever you need two parallel environments — staging vs production, A vs B, blue vs green.

The link between a backend and its deployment is 1:1. A single backend cannot host two concurrent deployments. To run two parallel environments, fork the backend first: copy the graph (with whatever changes), then deploy each fork independently. The two backends share the underlying component releases and differ in vertex parameters, vertex file bindings, or graph topology — whatever the parallel environment requires.

The 1:1 link keeps the production state unambiguous: what is running on a deployment is whatever graph that backend currently points at. Two parallel environments are two separate objects rather than two views of one object, so the operation history per backend, the recorded change history per environment, and the live tuning per deployment are each independent.

See Deployments for the 1:1 link in detail.

What a backend owns and what it does not

A backend owns its vertices (pinned release versions plus parameter, file, and constraint bindings), its edges, its endpoint aliases (the names callers use to reach specific output ports), and its operation log (the recorded history of every change). It also owns its display identity and its workspace ownership.

It does not own the component implementation (that lives in the component release), the running containers (those live in the deployment), the public URL (that lives on the deployment, derived from the backend's endpoint aliases), the team membership (that lives in the workspace), or any per-environment runtime state. The backend is the checked composition; the runtime, the observable state, and the externally reachable surface belong to the deployment and the workspace.

Drivers align against endpoint aliases

A backend does not carry a driver_id. Alignment is implicit: a backend satisfies a driver when, for every RequiredEndpoint the driver declares, some vertex's endpoint-alias map names that endpoint at the matching role, with a transport the driver accepts and the correct direction. The alias map is the decoupling seam — the driver describes what the backend exposes, the vertex's pinned component release decides how, and the operator can swap the implementation behind an alias without breaking the contract.

The same alignment is consumed by two surfaces an application pins: the live wire contract (needs_driver, transport per UX need) and the capture and replay contract (taps_driver, always HTTP, both directions). When a recording starts against the same backend, the platform resolves the taps_driver's required endpoints through the very same alias map and overlays HTTP tap URLs on the matched vertices at deploy-resolve — the backend graph is never mutated.

See Drivers for the contract layer and Replays for the capture surface that consumes taps_driver through the alias map.

Where this fits

Backends are where the platform's separation of concerns comes together. Components are reusable, releases are immutable, types are checked end-to-end, the operation log is reproducible, and deployments are decoupled from definitions. The backend is the primitive that composes those guarantees into a single workflow. Every product on Pipelogic is one or more backends — pinned, typed, event-sourced, redeployable — and that uniformity is what keeps a product's composition inspectable and reproducible as it grows.

Related

  • Components — the released capabilities a backend pins.
  • Types — the contract the platform validates the graph against.
  • Backend operations — the operation log in detail.
  • Deployments — the runtime side of the 1:1 link.
  • Leases — ephemeral envelopes around test backends.
  • Solutions — backend endpoints and the consumers they serve.
  • Quickstart — route picker for your first task.

Was this page helpful?