aquarium the live glass box for your LLM stack

Aquarium is a live glass box for the LLM inference path. You chat on the left. On the right the machinery works in the open: the request path drawn in motion, a log of every event that carried the conversation, and a narrator that explains each hop. It sits in front of the LiteLLM gateway you already run — nothing new goes into the inference path — and shows what happens to every request: what gets checked, what gets cached, what the guard decided, and how many tokens the model actually spent.

the request path this page's diagram runs on stratum-engine — the same pinned revision the app ships · click a box

The top two rows are the runtime — the boxes a request actually passes through. The bottom row is the glass box itself: the parts that make the machinery visible. Boxes that belong to a component — the narrator, the inspector, the themes, the schema, replay — open into it: double-click a box, or press its enter button.

deploy

One container, one compose file, one config file. The container runs unprivileged for its whole life (only a chown runs as root), its dependencies are pinned, and every deployment shape is an environment variable, not a code change.

three postures

postureone line of configwhat it is for
open — (the default) The lab, on a trusted network. Plain HTTP, no auth.
shared credential AQUARIUM_AUTH=user:pass UI and API behind HTTP Basic. The same container serves HTTPS when you mount a certificate (AQUARIUM_TLS_CERT / AQUARIUM_TLS_KEY).
identity front it with your proxy SSO, OIDC/SAML, MFA via the access layer you already run — Authelia, Authentik, oauth2-proxy, Caddy, a ZTNA broker. Aquarium deliberately implements no accounts.

One setting is not optional on a shared network: AQUARIUM_CALLBACK_TOKEN. It gates the callback endpoint — without it, anything that can reach the relay can inject events — and in the open posture it gates settings writes too.

private CA, both directions

Enterprise deployments usually terminate TLS with their own CA. Aquarium has a knob for each direction:

the egress policy

The relay is an outbound proxy for your gateway, so its outbound targets are validated at config-save time and on every use:

targetrule
loopback (127.0.0.0/8, .localhost)refused unless AQUARIUM_ALLOW_LOOPBACK=1 — the one-line change when the gateway runs on the same box
link-local (169.254.0.0/16)never — that is the cloud metadata range
unspecified, reserved, CGNAT (100.64.0.0/10)refused
hostnameschecked against what they resolve to — anything.example pointing at 127.0.0.1 is refused like the literal address
RFC1918 LAN addressesallowed — a homelab gateway on 192.168.x is the normal case

Upstream error responses are relayed as fixed text, never as upstream body, so a re-pointed base URL can't be used to read an internal service's content through the page.

the live wire under auth

The page's event stream is SSE, and EventSource cannot carry an Authorization header — so the page mints a wire ticket (GET /api/wire, gated exactly like the rest of the API) and opens /api/events with it. Tickets are bound to the session they were minted for, expire in two hours, and a stalled subscriber's queue is bounded — drop-oldest, so nobody can wedge the relay. /api/health reports counts only, never session ids.

data and limits

the turn document

One document shape, three producers. The chat pane, the Langfuse reader, and file import all emit the same events — aquarium.turn.v1 — and the viewer consumes nothing else. A session is portable JSON: export one from the running app, import it anywhere, or sweep it straight from Langfuse. Replayed turns render through the same code path as live ones.

events

eventwhen it happens
request_sentthe request leaves the relay for the gateway, with its full message list
deltaa streaming chunk arrives (streaming transport only), with its parsed content
response_completethe response settles, with usage and timing (wall time, TTFT)
errora leg fails; the error text is carried, never the upstream body
callback_receivedthe gateway's logging payload arrives on the wire — guard verdict, cache state, trace reference
trace_refa trace reference lands, binding the turn to the trace store
session_open / turn_start / turn_endthe session and turn boundaries the viewer groups everything into

provenance

Every narrator step carries an honesty label, because the diagram shows more than the wire says:

labelmeaning
observedbacked by events in the log — the wire says so
asserteddeclared by the topology — you said your deployment looks like this; the wire can't confirm it
inferreddeduced from evidence — the narrator shows its evidence

A node the wire never mentions reports that honestly in the inspector, instead of inventing numbers.