Observability
One OTel trace per spawn, GenAI token telemetry, real-time anomaly detection — wired through standard OpenTelemetry.
One trace per spawn
Every run opens a single OTel trace. Spans from every entry point use the same attribute keys, defined in internal/middleware/span.go and stamped by a single EnrichSpan helper so they never drift between HTTP and gRPC code paths:
lantern.tenant_id # on every span, both tiers
lantern.user_id
lantern.run_id
lantern.step_id # per-step spans in the inline executorOn the microVM tier the manager and harness additionally stamp vm_id, isolation_class, and agent_instance_id (the per-spawn Ed25519 identity — see Identity & secrets). A durable resume after a crash re-joins the same trace_id — the full lifecycle is one coherent timeline.
Enabling OTel
Export is env-gated. Set the endpoint and traces flow; leave it unset and tracing is a no-op (zero overhead):
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317
# or
LANTERN_OTEL_ENABLED=1 # uses default localhost endpointGenAI semantic conventions
LLM steps are annotated with OTel GenAI semantic-convention attributes — including reasoning tokens and cache tokens, not just plain input/output counts. Per-step cost attribution and model-usage breakdowns work out of the box with any OTel-compatible backend.
OTel span attribute contract
Every span emitted anywhere in the stack uses these keys, set via internal/middleware.EnrichSpan. They are no-ops when telemetry is disabled.
| Attribute | Key | Set by |
|---|---|---|
| Tenant | lantern.tenant_id | HTTP enrichment middleware + gRPC tracing interceptor |
| User | lantern.user_id | Same |
| Run | lantern.run_id | Same + inline executor |
| Step | lantern.step_id | Inline executor per step |
| Agent name | lantern.agent_name | Inline executor + model-router |
| VM ID (microVM) | vm_id | Scheduler / manager spans |
| Isolation class (microVM) | isolation_class | Manager spans |
| Agent version (microVM) | agent_version | Manager spans |
| Model used | model_used | Model-router completion span |
| Cost USD | cost_usd | Model-router completion span |
| Tokens in / out | tokens_in / tokens_out | Model-router completion span |
The five lantern.run.* metrics
The inline executor emits five OTel metric instruments via the lantern.runtime meter (internal/middleware/metrics.go). They are no-ops when the global MeterProvider is unset (the default no-op provider is safe to import with zero overhead).
| Instrument | Type | Attributes | Description |
|---|---|---|---|
lantern.run.step.duration | Histogram (ms) | agent_name, node_type, tier, outcome | Wall-clock duration of one workflow step, including all retry backoff. outcome is ok, failed, or retried. |
lantern.run.step.retries | Counter | agent_name, node_type, tier | Extra attempts beyond the first. Only incremented when retryCount > 0. |
lantern.run.replay.skips | Counter | agent_name | CompletedStep cache hits during crash-resume. Each hit means one node was skipped rather than re-executed. |
lantern.run.budget.blocks | Counter | agent_name | Runs denied by the agent's hard-fail budget policy (HTTP 402). |
lantern.run.total | Counter | agent_name, tier, status | Total runs dispatched through the inline executor. tier is shared or microvm; status is succeeded or failed. |
Real-time anomaly detection
The runtime watches the live event stream for pathological shapes — a tool-call loop, a step retrying without progress — and surfaces them in real time. This is the early-warning layer for runaway runs.
Peer-service health sweep
A background loop in the control-plane TCP-probes its peer services every 60 s (LANTERN_HEALTH_SWEEP_INTERVAL; set "0" or "off" to disable). After 3 consecutive failures it declares a peer DOWN and sends one self-chat alert. It sends one more when the peer recovers. No alert storms — only state transitions fire notifications.
Services probed when their address env var is set:
model-router—LANTERN_MODEL_ROUTER_ADDR(only whenLANTERN_USE_MODEL_ROUTER=1or addr is non-default)runtime-scheduler—LANTERN_SCHEDULER_GRPC_ADDRruntime-manager—LANTERN_DEFAULT_MANAGER_ADDRworkflow-engine—LANTERN_WORKFLOW_ENGINE_ADDR(optional)
Read the current snapshot:
GET /v1/system/health # JWT-authed{
"services": [
{
"name": "runtime-manager",
"addr": "localhost:50054",
"up": false,
"consecutiveFailures": 5,
"lastChecked": "2026-07-23T10:00:00Z",
"lastTransition": "2026-07-23T09:57:00Z"
}
]
}Metrics endpoint
Per-VM live stats for the caller's tenant:
GET /v1/runtime/metricsReturns a vmMetricsDTO array with vmId, state, node, az, isolationClass, promMetrics (raw Prometheus text from the harness), and timestamps. Per-instance detail: GET /v1/runtime/vms/{id}. Live log stream: GET /v1/runtime/vms/{id}/logs (SSE). The dashboard runtime page renders all three.
Gateway and model-router traces
The gateway emits one span per HTTP request (gateway.request, tagged with tenant_id) via OTLP/HTTP. The model-router emits one span per routing call tagged with tenant_id, run_id, step_id, model_used, tokens_in/out, cost_usd, and escalated via OTLP/gRPC. Both honour inbound W3C traceparent, so spans join the caller's distributed trace automatically.
lantern-TODO-needs-instrumentation group in infra/monitoring/prometheus/alerts.yml until the histogram metric ships.Prometheus alerts, dashboards, runbooks
Production monitoring artifacts live in infra/monitoring/:
| Group | Alerts | Source |
|---|---|---|
lantern-scheduler | SchedulerDown · SchedulerNoLeader · SchedulerScheduleErrorRateHigh · SchedulerQuotaRejectionSurge · SchedulerNoRegisteredNodes | runtime-scheduler :8085/metrics |
lantern-liveness | ControlPlaneDown · ControlPlaneNotReady · GatewayDown · ModelRouterDown | up scrape + blackbox /readyz |
lantern-postgres | PostgresExporterDown · PostgresConnectionSaturation · DataPlaneHeartbeatStale · CronScheduleOverdue | postgres_exporter + custom queries |
Eight operator runbooks cover every active alert plus the DB restore procedure, linked from each alert's runbook: annotation in alerts.yml. Grafana dashboards: grafana/platform-overview.json and grafana/data-plane-runtime.json.