Infrastructure and sidecars
How Weft turns stateful services into nodes you can wire into a graph.
Most AI systems are not just code. They are code plus a handful of stateful services: a database, a cache, a vector store, a browser pool, a WhatsApp bridge, whatever. In a traditional stack, those live in a different universe from your application code. You provision them with Terraform, connect to them via env vars, manage their lifecycle in a separate tool.
In Weft, they are nodes in the same graph as everything else. Drop a PostgresDatabase into your project, wire it to the memory nodes that need it, click Start on the infrastructure strip above the Run button. The platform provisions a real instance on Kubernetes, waits for it to be healthy, and hands you back a typed connection handle. The consumer nodes use the handle, the platform handles the rest.
The mental model
An infrastructure node in Weft is a declarative package that bundles three things.
- Kubernetes manifests. Raw JSON for the resources the service needs. Typically a
Deployment, aPersistentVolumeClaim, and aService. These are written once, hardcoded in the node's source file, with placeholders like__INSTANCE_ID__that the platform fills in at provision time. - A sidecar image. A Docker image the node deploys alongside the main service. The sidecar speaks a small HTTP protocol that Weft knows how to call. This is the only thing Weft talks to: it never talks to Postgres or RabbitMQ or WhatsApp directly, it talks to their sidecar.
- An action endpoint. A port and a path where the sidecar exposes its HTTP API. Consumer nodes hit this endpoint via a small retry-enabled HTTP client.
When you click Start, the platform fills the placeholders, applies the manifests to the target cluster, waits for the sidecar to report healthy, queries it for runtime values (connection URLs, instance IDs, whatever the sidecar wants to expose), and emits those values on the infrastructure node's output ports. Downstream nodes read those ports like any other typed connection.
The sidecar protocol
Sidecars are the secret to the whole thing. They are small HTTP services with three endpoints.
That is the entire contract. Anything that can be wrapped in a Docker image and expose those three endpoints can become a Weft infrastructure node, in any language. The reference sidecars in the repo are written in Rust, JavaScript, and whatever made sense for each service. The postgres-database sidecar is a Rust service that wraps Postgres. The whatsapp-bridge sidecar is a Node.js service that wraps a WhatsApp library.
Your consumer nodes (Memory Store, Memory Query, Send WhatsApp Message, whatever) never talk to the underlying service directly. They take a connection handle as input, build an InfraClient from it, and call execute_action("put", {...}). The sidecar handles the real work. This keeps consumer nodes stupid and portable.
Why not just write SQL directly
This is the question everyone asks, so let me answer it.
You could have a "Postgres node" that takes a connection string and a SQL query and runs it. That is fine for one-off queries, and Weft has nodes like that for when you need them. But the sidecar pattern buys you things a raw connection does not.
- Capabilities, not drivers. The sidecar exposes what you want to do (store a key, query by prefix, delete a record), not how to do it. The same capability can sit on top of Postgres today and on top of Redis tomorrow without changing any of the consumer nodes or any Weft code. Durable KV, say, is a contract, and the Postgres sidecar is one implementation of that contract.
- One security surface. The sidecar is the only thing that touches the database. It enforces input validation, rate limiting, and schema. Weft users cannot accidentally write a destructive query, because there is no query language exposed, just typed actions.
- Language freedom. The sidecar can be in whatever language makes sense for the service. The WhatsApp bridge uses a Node.js library that has no good Rust equivalent, so the sidecar is JavaScript. Postgres has a good Rust crate, so its sidecar is Rust. Weft does not care.
- Lifecycle hooks. The sidecar can do startup work, schema migrations, health checks, graceful shutdown. A raw connection to a database has none of that.
The lifecycle
A Weft project has three layers, each with its own lifetime. The platform tracks which layer each node belongs to and runs them at the right time.
- Infrastructure. Long-lived sidecars. You start them once, they stay running across many executions. Stop them to pause (data is retained on the PVC). Terminate them to destroy them permanently (PVC goes away, all data is gone). When an execution starts, infra nodes are already up and emitting handles.
- Triggers. Persistent listeners. Cron, API endpoints, incoming messages. Activated once, fire executions when events arrive.
- Execution. Everything else. LLMs, code, human queries, data processing, sidecar calls. Ephemeral.
Dependencies flow one way: infra nodes can have data nodes as upstream inputs (a Text node naming the database, a Number node sizing the disk). Triggers can depend on infra (a WhatsApp bridge infra node feeding a WhatsApp receive trigger). Triggers cannot be upstream of infra. The platform enforces this.
A concrete example: Postgres durable KV
The PostgresDatabase infrastructure node ships a bundle of three Kubernetes manifests. A PersistentVolumeClaim for durable disk. A Deployment with two containers, Postgres itself plus the sidecar. A Service that exposes the sidecar port inside the cluster.
When you click Start on the db node, Weft:
- Generates a unique instance ID (
wf-abc-def, under the K8s 63-char label cap). - Replaces
__INSTANCE_ID__in every manifest with that ID. - Replaces
__SIDECAR_IMAGE__withghcr.io/weavemindai/sidecar-postgres-database:latest. - Injects ownership labels (
weavemind.ai/managed-by,weavemind.ai/user, etc.) onto every resource. - Applies the manifests to the target cluster.
- Polls the sidecar's
/healthuntil it is ready. - Calls the sidecar's
/outputsto get the endpoint URL and instance ID. - Emits those on the node's output ports.
From that point on, db.endpointUrl is a real URL pointing at a real sidecar that knows how to store keys in a real Postgres database. The MemoryStoreAdd node uses it without knowing any of that.
Where the pods actually run
Infrastructure is optional. A project with no infra nodes runs just fine without Kubernetes. When you need infra, the platform provisions it wherever you tell it to.
- Local dev. A local kind cluster. Set
INFRASTRUCTURE_TARGET=localwhen you run./dev.sh server, and the platform provisions pods against your local kind. Good for end-to-end testing without cloud costs. - Your own cluster. Point the platform at any Kubernetes cluster you control. Infra pods run there, inside the namespaces and quotas you set. Your ops team keeps control, Weft just asks the cluster to run things.
- WeaveMind Cloud. The managed option. The platform provisions into the cloud cluster, you pay per resource, we handle the rest.
The same Weft project code works against all three. The target is a deploy-time choice, nothing in your graph changes.
Starting, stopping, watching
As soon as a project contains an infrastructure node, the Builder shows an infrastructure strip above the Run button. It summarizes the current state of your infra (one of not provisioned, starting, running, stopped, failed) and gives you Start and Stop controls. You cannot click Run until the infra is in the running state, because the consumer nodes would have nothing to talk to.
Each infra node also shows its own live status inline in the graph view, next to the node itself. Clicking the node opens its details with the instance ID, the endpoint URL, and any other values the sidecar exposed through /outputs.
The state machine is:
- Not provisioned. The node is in the code but no pods exist.
- Starting. Manifests are being applied, the sidecar is booting, health checks are pending.
- Running. The sidecar is healthy, the endpoint URL is live, consumer nodes can talk to it.
- Stopped. Pods are scaled to zero, data on the PVC is retained. Starting again brings the same data back.
- Terminated. Pods and PVC are deleted, all data is gone. Starting creates a fresh instance.
- Failed. Something went wrong during provisioning or the sidecar is crash-looping. The Builder surfaces the pod status so you can see what happened.
Each transition is a real Kubernetes operation against your target cluster. The Builder polls pod status in the background, so you see unhealthy or crash-looping infra as soon as the cluster does.
Building your own sidecar
The pattern is general. If you have a stateful service that does not exist as a Weft node yet, you can add one by:
- Writing a small HTTP service that implements
/action,/health,/outputs. Any language. - Packaging it as a Docker image.
- Writing a Rust infrastructure node in the catalog (
catalog/<category>/<name>/backend.rs) that returns the Kubernetes manifests it needs and points at your sidecar image. - Writing the matching
frontend.tswith the node metadata (ports, icon, category). - Writing the consumer nodes that take the infra's endpoint URL as input and call your sidecar actions.
The reference examples (Postgres, WhatsApp) live in sidecars/ in the repo, along with a minimal Rust and JavaScript starter. Copy one, tweak it, ship a PR. Full walkthrough in the contributing guide.
What's next
- Files and media: how non-text data moves through a project.
- Mock nodes and test configs: how to iterate on code that depends on infra without paying for real pods.
- Contribute: how to build and ship a new sidecar and infra node.