Observability¶
vornik is built to run unattended, so it gives you several ways to see what it's doing: a set of operator dashboards in the Web UI, and a Prometheus metrics endpoint for your own monitoring stack.
The dashboards¶
Home¶
The Web UI landing page (/ui/) is an operator front page of live tiles:
active tasks (queued, leased, running), active chats, a countdown to the next
autonomy evaluation per project, the response cache's savings (calls Vornik answered itself, across all cached entries), a memory
pipeline snapshot, and an Inbox tile for anything that needs you.
Insight¶
The Insight area collects the analytical views:
- Spend (
/ui/spend) — the cost deep-dive (below). - Trends (
/ui/insights/trends) — daily throughput and success-rate over a trailing window, plus a recovery-events series when recovery tracking is wired. - Tool budget (
/ui/insights/tool-budget) — a histogram of how many tool calls executions actually use, plotted against the complexity-tier budget, with mean / P50 / P95. Use it to tune budgets to reality.
Health¶
Admin-gated health tabs under /ui/admin/health/ cover the daemon's operational
state: leases (lease status and recent lease transitions — the surface for
diagnosing stuck or lost work), the stuck-execution watchdog, MCP server
reachability, runtime probes, and cluster leader/heartbeat status.
Spend and cost-efficiency¶
The spend dashboard (/ui/spend) slices cost by window (24h / 7d / 30d),
project, source, task, role+model, and API key. Alongside total cost and
token counts it surfaces a few efficiency signals.
Embedding spend appears under the role Memory · Embedder and the source Memory embedding. It is included in every rollup on the page — a deployment using a hosted embedder can see what ingest and retrieval cost, not only what chat cost. The LLM reranker that reorders recall candidates appears as Memory · Reranker (source Memory reranking).
- Input ratio — prompt tokens as a share of total; a persistently high ratio flags context bloat.
- Cache hit ratio and dollars saved — how much prompt/embedding caching is actually saving.
- Estimated token counts — a
~before a token figure means at least one call behind that line reported no token count, so the total is partly derived from text length rather than measured by the provider. Embedding backends differ here: an OpenAI-compatible endpoint reports usage and Bedrock Titan reports an input-token count, while Bedrock Cohere reports neither. The marker exists so an inferred number is never mistaken for a measured one when reconciling a provider bill. - Effective cost per success and its drift ratio — cost per useful result (spend divided by successful outcomes), and how the recent window compares to a baseline. A cheap model that fails often can cost more per success than an expensive one that doesn't; the drift badge (green/amber/red) flags when that's getting worse. See Cost and caching.
Spend per API key¶
The Spend per API key table attributes LLM cost to the key that
authenticated the call, so you can see which integration is spending — a
companion session, a CI caller, a script hitting POST /tasks.
Read it as per key, not per person. A key is minted per (project, client, session), so one teammate routinely holds several and each shows as its own row; plenty of keys have no person behind them at all. Vornik's user accounts (admin UI logins, sessions, approvals) are a separate concept and are not what this table groups by.
Rows land in Unattributed when the spend has no reliable key behind it: dispatcher chat, memory background workers, the project wizard, UI authoring assist, and the fix-it doctor. Those are shown as one bucket rather than guessed at. Attribution starts at the migration that added it — spend recorded before then stays unattributed, with no backfill.
A key's own spend cap (vornikctl companion grant --budget-usd) is a
narrower number than its row here: the cap counts spend on tasks the key
created, while the table also includes task-less calls the OpenAI-compatible
chat proxy attributes to it. Both resolve the key the same way; they differ only
in which rows they count.
Upgrade note: caps may start binding where they didn't
Before per-key attribution existed, the cap could only recognise a key
through the marker left by companion delegate, so tasks a key created
through the REST API (POST /tasks) were invisible to it — that spend was
effectively uncapped. After upgrading, both paths count. A key whose cap
looked like it had headroom may be at or over it, and its next delegate
will be refused with BUDGET_EXCEEDED. Nothing is retroactively billed;
only the gate's view widened. If you rely on caps, check
vornikctl companion list against the per-key rows on /ui/spend before
upgrading and raise any cap that was sized against companion traffic alone.
From the CLI, vornikctl cache-stats reports cache effectiveness. (Note:
vornikctl cpc is the cross-project-call ledger, not a cost command — there is no
vornikctl spend.)
Prometheus metrics¶
vornik exposes Prometheus metrics at /metrics. Enable the dedicated metrics
server and point your scraper at it:
metrics:
enabled: true
addr: ":9090" # dedicated metrics listener; scrape http://<host>:9090/metrics
require_admin: false # when true (with auth on), the main-port /metrics needs a token
# scrape_token: "…" # a read-only credential accepted alongside the admin key
/metrics is also mounted on the main API port. When you run with
authentication on and set require_admin: true, the
main-port endpoint requires the admin key or a dedicated scrape token; the
separate metrics.addr listener is the intended scrape target — bind it to a
trusted interface.
Every /metrics that answers without a credential (the dedicated listener
always, and the main-port endpoint unless require_admin is set with auth on)
serves only requests addressed to an IP address, localhost, the daemon's
public base URL host, or a name listed in server.accepted_hosts. This stops
a web page from reading your metrics through DNS rebinding. If your scraper
reaches vornik by another name (for example a compose service name such as
vornik:9090), add that name to server.accepted_hosts; otherwise the scrape
gets 403.
The exported metrics cover, in plain terms:
- Task and execution lifecycle — active, started, completed, failed, cancelled, retried counts, and durations.
- Queue, scheduling, and leases — queue depth by status, how long tasks wait before running, leases acquired and expired, and recoveries of expired leases.
- LLM cost and tokens — dollar spend per project and per model, prompt and completion tokens, tool-calling iterations, and cache savings.
- Model quality — per role and model: success rate, effective cost per success, and rates of parse failures, schema violations, refusals, and degenerate loops.
- Tool calls, autonomy, and cross-project orchestration — tool invocation counts, autonomy approval resolutions and configured caps, cross-project call and spawn counts, and outcomes of agent-recorded backlog deposits (accepted, or rejected as a duplicate, a secret hit, over the per-task cap, or in cooldown).
- Scheduled updates (task-kind reminders) — task-kind fires per project,
outcome deliveries by success/failure, fires skipped because the prior run
was still going, delivery-time channel-send failures, outcomes that had
nowhere to go (no channel resolver, or the channel not configured), and stuck-
firingrows the crash-recovery sweep reclaims. See the vornikctl reference (remindersgroup) for thetextvstaskkinds and pause/resume. - HTTP and process — request counts, durations, in-flight requests, plus standard Go/process collectors.
OpenTelemetry tracing can be enabled alongside metrics with tracing.enabled and
tracing.endpoint.
See the Configuration reference for every
metrics.* key and default.