Skip to content

Reference

Self-host

Machinaut is offered as a hosted service, but the control plane is the same image set you can run yourself. This page is about the two decisions that precede that: whether you need the control plane at all, and — if you do — how to deploy it.

A solo operator working locally needs only Lodestar OSS. The control plane earns its keep the moment a team has to govern many agents across many projects.

Do you even need the control plane?

Lodestar OSS is complete on its own for a single operator. It wraps your agent, records the chain, holds risky actions, and ships a local viewer to read all of it — on your machine, no server, no account. If that's your whole world, you don't need Machinaut.

You reach for the control plane — hosted by us, or self-hosted — when:

  • A team shares the record. Sessions from every project, searchable in one place, not scattered across operators' loopback viewers.
  • Someone other than the agent's author approves. Held actions need an inbox a second person can reach — the approval write-path.
  • You govern a fleet. Cross-session, cross-project calibration and sentinel rollups; an org-scoped audit log; policy drafting.

The open-core boundary — what's OSS, what Machinaut adds

Lodestar OSS ships the explorer, redaction, signing, hashing, its storage (Postgres), OTel export, and the calibration math — the whole read-and-verify core. Nothing on this page reimplements any of it. Machinaut adds the hosting: multi-tenant ingestion, indexed search, the approval write-path, and the fleet / governance surfaces. When you self-host Machinaut, you're running that layer — not a fork of Lodestar.

Two images, one host

The control plane is two Bun-runtime images behind a single Ingress host — same-origin, no CORS. The explorer image doubles as the front door: its nginx serves the SPA and proxies /v1 + /healthz to the API service, so a single port-forward to the explorer gives you a fully working app without an Ingress controller. /readyz and /metrics are deliberately not routed by the Ingress — they stay internal for kubelet probes and Prometheus scrapes.

ServiceWhat it isPort
machinaut-apiBun + Elysia control plane. Serves /v1, /healthz, /readyz, /metrics.4319
machinaut-explorernginx serving the SPA + proxying /v1 and /healthz to the API.8080

The manifests

Deployment is Kustomize, no templating engine — plain, legible YAML. A base plus two overlays:

  • base/ — namespace, the api + explorer Deployments / Services, Ingress, ConfigMap, a suspended prune CronJob, HPA, PDB. It is not deployable on its own — no Secret, placeholder host. Always go through an overlay.
  • overlays/dev — adds an in-cluster Postgres, a dev Secret, a demo-seed Job, and a plain-HTTP posture.
  • overlays/prod — production posture (MACHINAUT_ENV=production), TLS Ingress, GHCR image refs; expects an operator-created Secret and an external managed Postgres.

Building the images

Both images are Bun-runtime (argon2 + Bun.sql pin them to Bun, zero native modules). Two wrinkles:

  • The control-plane image needs the unpublished @qmilab/lodestar-* packages in its build context — supplied as a named build context lodestar pointing at a local Lodestar checkout.
  • The explorer SPA is built outside Docker first (bun run build), then packaged into nginx — TanStack Start's SPA build prerenders the shell via a local server that buildkit's sandbox won't allow, so the Vite build can't run inside the Dockerfile.
build both imagesbash
# control-plane API image (needs a local Lodestar checkout as a build context)
docker build --build-context lodestar=/path/to/lodestar -t machinaut-api .

# explorer image — build the SPA first, then package it into nginx
(cd explorer && bun install && bun run build) && docker build -t machinaut-explorer explorer

Images are published only on a release tag — and need private access

CI builds both images on every push, but publishes to ghcr.io/<owner>/machinaut-{api,explorer} only on a v* release tag (routine merges build-only). Building the control-plane image at all requires read access to the private Lodestar repo (a LODESTAR_PAT secret). Self-hosting today therefore means building from source with that access — it is not yet a public, pull-and-run image.

Dev — one command

The dev overlay is fully self-contained: an in-cluster Postgres, plain-HTTP posture, and a Job that seeds a full demo chain. Signups bootstrap into org dev, so a signed-in user sees the same seeded demo as the default bearer-key view. make dev-down tears the cluster down.

local cluster on kindbash
make dev-up      # build images → kind cluster → load → apply overlays/dev → wait
kubectl -n machinaut port-forward svc/machinaut-explorer 8080:8080
# open http://localhost:8080 → the seeded `demo` project (bearer key mk_dev_local, tenant dev)

Production

Kustomize ships no Secret in prod — create it first, pointing at your managed Postgres (the in-cluster StatefulSet is dev-only):

1 · create the Secretbash
kubectl create namespace machinaut
kubectl -n machinaut create secret generic machinaut-secrets \
  --from-literal=DATABASE_URL='postgres://user:pass@your-db-host:5432/machinaut' \
  --from-literal=MACHINAUT_API_KEYS='{"mk_live_xxx":{"tenant_id":"acme"}}'
# (optional: webhooks, OTLP headers — see base/secret.example.yaml. Managed approver keys
#  are the planned convenience tier — the posture is customer-held; see the approval relay contract.)

Then edit overlays/prod/kustomization.yaml (image newName / tag → your GHCR ref at an immutable digest) and the host in the prod overlay, and apply:

2 · review + applybash
kubectl kustomize deploy/k8s/overlays/prod    # review
kubectl apply -k deploy/k8s/overlays/prod

Production refuses to boot on a footgun

MACHINAUT_ENV=production makes the API refuse to start on the dev key, a non-Secure cookie, ephemeral managed keys, or a missing DATABASE_URL — so a misconfigured prod deploy fails loud instead of running insecure.

Operational notes

Migrations

Run in the api initContainer (bun run migrate) on every rollout — advisory-locked and idempotent, so concurrent replicas serialize. For a CD flow that prefers a discrete pre-deploy step, apply the standalone migrate Job through its overlay (overlays/{dev,prod}/migrate), never apply -f the raw base Job.

Probes & metrics

Readiness is /readyz (a store-backed DB ping → 503 when Postgres is down, so a severed pod leaves rotation); liveness and startup use /healthz. /metrics (Prometheus) is scraped off the api Service annotations and is never exposed through the Ingress.

Retention, autoscaling, proxy

  • The prune CronJob is suspended by default (retain forever). Set MACHINAUT_EVENT_RETENTION_DAYS and flip suspend: false to enable.
  • The HPA needs metrics-server; absent it, the Deployment's replicas stand.
  • MACHINAUT_TRUST_PROXY=1 lets the API read the ingress-set X-Forwarded-For for the per-source login throttle; the Ingress sets proxy-body-size: 26m to admit the 25 MiB ingest ceiling.

Where to go next

The two wire contracts your self-hosted plane must honor are the ingest contract and the approval relay contract; the full surface is the API reference. Per-capability launch status lives on the roadmap.