Solutions
TL;DR
- A solution is the whole stack of a shipped Pipelogic product: a component (reusable typed capability) gets pinned into a backend (typed graph), which runs as a deployment, and is reached either by direct callers or through a application (UI bound to deployed endpoints by stable aliases).
- A driver is the named, reusable contract an application pins. The application references two:
needs_driver(live wire contract — transport per UX need) andtaps_driver(capture and replay contract — always HTTP, both directions). A backend satisfies a driver when its endpoint aliases line up with every required endpoint the driver declares — nodriver_idlives on the backend, the alignment is implicit. - The work of building a solution is mostly composition, not coding. Components in the catalog get wired into backends; backends become deployments; deployments expose endpoints; applications bind to those endpoints. Custom code shows up only where the catalog does not already cover the capability.
- Endpoint aliases keep an application decoupled from vertex IDs. A backend maps
(vertex, endpoint-name)to a stable role string; the application resolves the aliasuploadagainst the deployment instead of pinning a specific vertex's endpoint. - The order of operations for most product work is reuse before swap before build: start from a published backend or component in the Solutions catalog, swap a provider when the role fits but the implementation should change, and write custom code only when no catalog entry covers the capability.
- A solution is ready when expected behaviour is observable in the live runtime — outputs flowing, container logs healthy, generated files where they should be — not when the graph merely exists.
What "solution" means here
A solution is the composition of all four layers — component, backend, deployment, application (or direct caller) — into a product that an end user interacts with. The term names the whole stack rather than any single primitive.
The contract is precise because the layers are many-to-many: the same component can sit inside dozens of solutions, the same backend can serve more than one product surface, and the same deployment can host more than one application's endpoint bindings. A single word for the assembled product means a conversation about "this product" does not have to enumerate which component versions, which backend revision, which deployment, and which application build it refers to. The word means all of those, joined by the alias and binding contracts the platform enforces.
Each layer does one job. Components stay generic, backends compose them, deployments run them, and applications surface them. The boundaries are strict: a component does not know which backend uses it, a backend does not know which deployment runs it, and a deployment does not know which application resolves its endpoints. That separation is what makes the same primitives reusable across many solutions.
Mental model — the four layers
┌─────────────────────────────────────────────────────┐
│ Application UI; resolves endpoint aliases │
├─────────────────────────────────────────────────────┤
│ Deployment the backend running on a runtime │
├─────────────────────────────────────────────────────┤
│ Backend typed graph + per-vertex bindings │
├─────────────────────────────────────────────────────┤
│ Component reusable typed capability │
└─────────────────────────────────────────────────────┘
Reading bottom to top: a component is a reusable typed capability with declared input slots, output slots, configuration parameters, and optional file slots. A backend is the product-specific graph that pins component releases as vertices, binds their parameters and files, connects their typed streams, and gives stable role names to the endpoints external callers will reach. A deployment runs that backend on a runtime, materialises the graph as containers, and publishes resolved URLs for each endpoint alias. A application is the UI bound to the deployment by alias — it asks for upload and events and gets back the live URLs without ever knowing which vertex or release is currently behind them.
A backend hosts at most one deployment at a time — the link is 1:1 by design, so that what is currently running is unambiguous. To run two environments in parallel (staging and production, A and B, blue and green), fork the backend first and deploy each fork independently. Each environment is then its own object with its own operation history, which keeps per-environment behaviour straightforward to audit.
See Components, Backends, Deployments, Applications for each layer in detail.
Endpoint aliases — the decoupling between graph and surface
Use this framing whenever a UI or external caller needs a stable URL.
A component declares the endpoints its vertex exposes when it runs. The input_image_http component declares an ingress endpoint that accepts image uploads; output_json_http declares an egress endpoint that emits each typed value as JSON; an LLM component declares an egress endpoint that streams generated tokens over a server-sent-events transport. Each endpoint has a component-local name, a port, a kind (ingress or egress), and one or more transports (HTTP, WebSocket, SSE, WebRTC, multipart) that carry it.
The component-local name is not stable across a solution. Vertex 1 in one backend might be vertex 7 in another. The endpoint name a component uses internally might mean nothing to the application. The backend layer fixes this by mapping (vertex, endpoint-name) to a stable role string through ppl backend change-endpoint-alias. The application talks to roles, not to vertices.
┌─────────────────────┐ ┌────────────────────┐
│ vertex 1 (ingress) │ │ application POSTs │
│ "image_input" │───────────── upload ─▶│ the image here │
└─────────────────────┘ └────────────────────┘
┌─────────────────────┐ ┌────────────────────┐
│ vertex 3 (egress) │ │ application subscribes │
│ "output_json_http" │───────────── events ─▶│ to events here │
└─────────────────────┘ └────────────────────┘
When the deployment comes up, it publishes the resolved URL for each alias. The application resolves upload and events against the deployment and stays decoupled from vertex IDs, component releases, and runtime placement. Swapping the underlying component release, renumbering the vertex, or changing the implementation entirely leaves the application working as long as the alias still exists.
This indirection is what lets an application survive backend changes: the alias is the contract, and the vertex behind it is an implementation detail. A backend mutation that touches a vertex does not break the UI as long as the alias it resolves remains in place.
Reuse, swap, build — in that order
Use this framing when planning a new solution.
Reuse first. Open the Solutions catalog and search for a published backend that matches the outcome the solution needs. If one fits, fork it into the workspace and iterate from there — the graph assembly is already done; what is left is parameter tuning, file binding, and maybe an application.
Swap when the role is right but the provider should change. A backend wired around a particular role — an OCR component, an image classifier, or an LLM — can usually be retargeted to a different provider in the same role by swapping the component release on that one vertex. The type system drives this: the platform preserves every parameter, file binding, and edge that still type-checks against the new release and drops anything that no longer does. Run the swap in plan mode first to see exactly what would be dropped; the apply step refuses by default whenever anything would be lost, so a breaking swap never lands silently — you confirm the drops explicitly before they commit.
Build last. Write a custom component only when no catalog entry covers the capability the solution needs, or when the logic is product-specific enough that a generic component would be the wrong abstraction. The catalog is indexed by category and modality so that "is the thing I need already here" is one search away; build fills the gaps the search exposes.
The order reflects how much work each move takes. Reusing a backend reuses an assembled graph; swapping a component changes one vertex; building a component adds new code to author, release, and pin. Reaching for reuse and swap before build keeps a solution on the catalog's tested surface for as long as the role allows.
How external APIs fit in
Use this framing whenever the solution needs to talk to a model server, a database, a webhook, a hardware device, or a third-party service.
External APIs are not a special category on Pipelogic. They show up as normal components with their typed inputs and outputs, and their handling of authentication, retry, transport, and rate limits lives inside the component. From the backend graph's perspective, a vertex that talks to a hosted model API and a vertex that runs a local ONNX model look the same: typed in, typed out, parameters and secrets bound, deployed as a container.
This uniformity means there is no separate plugin API. Pipelogic has no separate model-provider SDK, database connector framework, or message-broker integration layer — everything is a component, every component has the same contract, and every solution composes them the same way. Integration authors write small components rather than registering providers, and solution authors compose them through a single abstraction regardless of what each component talks to.
Reference snapshot — layer boundaries
| Layer | Owns | Does not own |
|---|---|---|
| Component | typed I/O, code, parameters, defaults, release metadata | the backend it runs in, the bindings on any given vertex |
| Backend | the graph, per-vertex parameter and file bindings, endpoint aliases, runtime overrides | the component implementation, the runtime, the application |
| Deployment | the live runtime for one backend, resolved URLs per alias | the backend graph definition, the application UI |
| Application | the UI, driver pins (needs_driver, taps_driver), alias resolution against a deployment | the backend graph, the deployment lifecycle, the component code |
| Driver | the named contract (required endpoints, required worker promises, required fills) every backend and application aligns against | the implementation behind any alias, the transport at runtime |
Component releases are immutable. A newer release of a component does not disturb a backend that pinned an older one — rebinding is an explicit backend edit, never a side effect of promotion.
When is a solution "done"
The graph existing is not the bar. A solution is done when expected behaviour is observable in the live runtime: representative inputs produce the right outputs, container logs are clean, generated files land where they should, and the application (when there is one) renders without manual recovery. That observation is the proof loop, and it is the same loop described in Prove behavior.
The proof step is what supplies the evidence behind a promotion decision. Production-ready means that evidence exists, not that the graph has been assembled.
Where this fits
Solutions are the platform's vocabulary for the shipped product as a whole. The four-layer separation is what makes that product assembleable, modifiable, redeployable, and reusable. The primitives — typed components, event-sourced backends, separated deployments, alias-bound applications — give each of those properties a concrete object to attach to.
Related
- Components — reusable typed building blocks.
- Backends — typed graph + bindings.
- Deployments — running backends + resolved URLs per alias.
- Applications — UIs that resolve aliases against deployments.
- Types — the contract enforced across all layers.
- Solution publishing — sharing a solution publicly.
- Quickstart — route picker for your first task.