Instrumented
the moment you write it.

You don’t wire up tracing — the runtime does. Every mutation, query, subscription, workflow, webhook and AI call auto-emits an OpenTelemetry span and feeds the metric registry. Read it in the in-app dashboard, or export it to whatever you already run.

observability
TypeScript
// nothing to wire — every primitive emits a span
// posts.create.mutation.server.ts
export default async (input, ctx) =>
  ctx.store.insert('posts', input)
//        └─ auto span: mutation.posts.create
//           + voltro_rpc_* metrics, one traceId
//           from the browser through the API

// export: OTEL_EXPORTER_OTLP_ENDPOINT=…  → OTLP
//     or  add @voltro/plugin-prometheus  → /metrics

What you get without instrumenting anything.

OpenTelemetry-native spans

Every primitive emits a span automatically — a per-request waterfall you can read without adding a single tracer call. Modes: console, otlp (auto-detected from OTEL_* env), or off, with an always-on buffer feeding the in-app dashboard.

A metric registry, built in

voltro_rpc_*, voltro_http_*, voltro_ai_* and more land in an Effect MetricRegistry — request counts, latencies, error rates, AI token spend — ready to export or scrape.

In-app traces & time-travel

The always-on buffer powers a Traces dashboard inside the app — inspect a request’s span tree and replay it, no external backend required to start.

Distributed tracing

One traceId flows from the browser through the API and back, so a slow interaction is a single trace end to end — not four disconnected logs you have to correlate by hand.

Export anywhere

plugin-prometheus exposes GET /metrics; plugin-datadog pushes metrics, logs and traces; plugin-sentry sends trace-correlated errors; plugin-logship ships structured logs to Better Stack / Axiom / Loki / any JSON intake.

CLI + DevTools inspection

voltro logs and voltro traces stream from the terminal; the DevTools dashboard surfaces routes, RPC, workflows, schedules, cache, logs, traces and per-plugin panels over /_voltro/inspect/*.

On by default, extensible by seam.

Plugins contribute observability too

Observability isn’t bolted on at the end — it’s emitted by the runtime and every first-party plugin, and a contributeObservability seam lets your own plugins add spans and metrics the same way. Turn it up to OTLP for production, leave it on console in dev, or read the in-app dashboard either way.

One trace, four layers.

one request
bash
// Nothing to instrument by hand — the span comes from the primitive.
// client.mutation  orders.ship       12ms
//   server.mutation  orders.ship     11ms   trace 4f9c…
//     store.transactional             7ms   trace 4f9c…
//       workflow.step  reserve        4ms   trace 4f9c…
//         http  POST /warehouse       3ms   trace 4f9c…

The identifier is the same in all four places, which is the only property that matters when something failed: you read one causal chain instead of correlating four systems by timestamp and hoping.

Observability, in depth.

What is instrumented without me doing anything?

Every procedure call, every mutation, every workflow step, every scheduled firing and every database query the runtime issues. Because those are declared primitives rather than anonymous functions, the framework knows what each one is and can time it, trace it and attribute it without you adding a span.

Traces are correlated end to end: a browser action, the rpc call it made, the handler, the queries beneath it and any workflow it started share a trace id. That is the difference between "this request was slow" and "this request was slow because of these three queries inside this step".

Logs carry the same correlation. A log line emitted inside a handler is stamped with the trace and the subject, so filtering by trace id gives you the whole story of one request rather than a keyword search across a day of output.

Do I have to send data to a vendor?

No. The default is a local inspect surface: `voltro inspect`, `logs`, `traces` and the dashboard read a running app directly over an authenticated endpoint. Nothing leaves your infrastructure, and there is no account to create before you can see what your app is doing.

When you do want a vendor, they are plugins rather than a rewrite: Prometheus scraping, deep Datadog and Sentry integrations, or shipping structured logs to Better Stack, Axiom, Loki or any HTTP sink. Each rides the framework's existing metric and log hooks, so adding one does not mean instrumenting your code again.

The exporters read the same unified metrics API the dashboard reads. That matters because the usual failure is two sources of truth — a dashboard that says one thing and an alert that fires on another — and here there is one registry behind both.

How do I debug something that already happened?

By reading a record rather than reconstructing one. A workflow keeps a journal of every step, its result and its timing, so a run that failed three days ago can be inspected and replayed. A dead-lettered run is a queryable state, not a lost message.

Row history is optional but available: with the row-history plugin every insert, update and delete on the tables you list is snapshotted, so you can ask what a row looked like at a moment, diff two versions, or restore one — the questions that otherwise become an archaeology exercise in backups.

Audit is a separate concern with a stricter guarantee: who did what, attributed to a subject, including whether the action was taken through an impersonated session — a mark no redaction rule can remove.

What about cost and behaviour of the AI parts?

AI calls are instrumented like anything else, with token usage and cost attributed per call, per procedure and per tenant. A cost budget is a declared primitive rather than a dashboard alert, so exceeding it is something the runtime can act on rather than something somebody notices later.

Agent runs are recorded and replayable against golden cases, so a prompt or model change can be evaluated as a regression test rather than judged by feel — and wired into a deploy gate that fails when quality drops.

Experiments and expectations are the same idea applied to behaviour: declare what you expect to hold, and let violations be reported where they happen rather than discovered from a metric moving weeks later.

What is instrumented for you.

Observability surfaces and what each one answers
SurfaceAnswers
Distributed tracesWhere the time went — correlated from browser action to handler to query to workflow step.
Structured logsWhat happened in one request, stamped with trace and subject rather than searched by keyword.
MetricsOne registry behind both the dashboard and the exporters, so alerts and panels cannot disagree.
Workflow journalsEvery step of every run, inspectable and replayable — including dead-lettered ones.
Row historyTime travel over listed tables: what a row was, what changed, and restoring a prior version.
AuditWho did what, attributed — including actions taken through an impersonated session.

Frequently asked questions

Do I need an observability vendor to see anything?

No. The CLI and dashboard read a running app over an authenticated local surface, so traces, logs, metrics and workflow runs are visible without an account or an egress path. Vendors are plugins you add when you want them.

Do I have to add spans to my handlers?

No. Procedures, workflow steps, scheduled firings and queries are instrumented because they are declared primitives — the runtime knows what each one is. You add a span when you want to mark something inside a handler, not to get the basics.

Can I use Prometheus, Datadog or Sentry?

Yes, as plugins over the framework's existing hooks. Prometheus exposes a scrape endpoint from the unified metrics registry; Datadog and Sentry integrate deeply, with errors correlated to the active trace rather than reported as standalone events.

How do I find out what a row used to look like?

With the row-history plugin, which snapshots every write on the tables you list. You can read a row as of a moment, diff two versions, or restore one — without treating a backup as a query interface.

Is AI usage and cost visible?

Yes, attributed per call, per procedure and per tenant, and a cost budget is a declared primitive the runtime can act on rather than an alert somebody reads afterwards. Agent runs are also recorded and replayable against golden cases as a deploy gate.

Observability spans the whole runtime.

Open the framework. See it for yourself.

Every primitive on this page is in the framework today. Clone the starter, run `voltro dev`, and have it on screen in two minutes.