Documentation · Operations
Dashboard
zolva dashboard serves a self-hosted, read-only web UI
where your team sees every interface at a glance: which agents exist, which tools
(your APIs) they can call, where they hand off, and the live flow of queries in and
out, session by session, step by step.
The dashboard adds zero new instrumentation. Everything it shows already exists in two places Zolva maintains by construction:
- Agent configs are the interface map. Each YAML declares an agent's model, tools, handoffs, and policies. Parsing the config directory yields the full static topology.
- The audit log is the data flow. Every step already flows
through the bus into the hash-chained audit log:
user_msg,model_call,tool_call,response,handover,feedback,channel. That is, literally, queries in and queries out.
mode=ro. It cannot write a row, so it can never
touch the hash chain it reports on.Install & run #
The dashboard ships as an optional extra, so the core package keeps its three frozen runtime dependencies:
$ pip install "zolva[dashboard]"
$ zolva dashboard agents/ --audit audit.sqlite
zolva dashboard: http://127.0.0.1:8600 (audit=audit.sqlite, read-only)
| Flag | Default | Meaning |
|---|---|---|
config_dir | none | Agent config directory; omit it and the topology panel is empty, everything else still works. |
--audit | audit.sqlite | Path to the audit DB, opened read-only. |
--host | 127.0.0.1 | Bind address. Localhost by default; see Security model before changing it. |
--port | 8600 | Port. |
Demo dataset #
The repository ships a deterministic seeder so you can see the dashboard fully populated before wiring your own app: three agents (collections, support, disputes), about 600 sessions over 14 days, roughly 4,000 hash-chained rows with tool calls, guardrail blocks, a cross-agent handoff path, and human handovers.
$ python examples/dashboard_demo/seed.py
wrote 3554 audit rows across 600 sessions to examples/dashboard_demo/audit.sqlite
$ zolva dashboard examples/dashboard_demo/agents \
--audit examples/dashboard_demo/audit.sqlite
To watch the live tail move, keep the page open and run
python examples/dashboard_demo/live.py in a second terminal: it appends a
fresh session every few seconds, and new rows flash into the feed. Because the seeder
writes through AuditLog.append, the demo DB has a valid chain, and
zolva scorecard works on the same file.
How it works #
Zolva's runtime already routes every observable step through the middleware bus, and the audit plugin appends each one to an append-only SQLite log where every row's hash covers the previous row's hash. The dashboard is a second, strictly passive consumer of that same log:
writes (live traffic) reads (dashboard)
channels -> orchestrator -> bus ==\
| || +----------------------+
guardrails hooks || | zolva dashboard |
| \/ | (FastAPI + one |
| audit.sqlite <===| HTML file, |
| (hash-chained, ro)| SQLite mode=ro) |
v +----------------------+
human handover ^
|
agents/*.yaml (models, tools, handoffs) ================/ topology
Three properties fall out of this shape:
- No new failure mode. The dashboard is not in the request path. If it is down, agents run exactly as before.
- Nothing to forget. There is no SDK call to add per step; if a step happened, the audit hook logged it, so the dashboard saw it.
- The evidence and the view are the same bytes. What your team watches is the same tamper-evident log a regulator would replay, and the header badge re-verifies the chain continuously.
The server side is one module (zolva/dashboard.py): four JSON
endpoints over the audit DB plus the config loader. The client side is one
self-contained HTML file with inline CSS and JS, no CDN, no webfonts, no external
requests, so it renders inside an air-gapped VPC.
The four views #
| View | Source | Shows |
|---|---|---|
| Interface topology | agent configs | Every agent with its model and policies, solid edges to the tools it may call, dashed edges for agent-to-agent handoffs, and the human-escalation path in amber. |
| Queries out | audit log | Tool-call volume by name, so teams see which internal APIs the agents actually hit, and how often. |
| Sessions, live | audit log | A live-tailing feed of sessions, newest activity first, with outcome pills (resolved, escalated, active). Click a row for the full step-by-step transcript: the query in, each model and tool call out, and the response or handover. |
| Scorecard | audit log | SARR, containment, escalations, step volume by day, top handover reasons, and the audit-chain verification badge. |
Live tail #
The audit table's autoincrement id doubles as a cursor. The page polls
/api/sessions?after_id=<cursor> every 2.5 seconds; the server
returns only sessions with rows past that id, plus the new global max id. One indexed
aggregate query per poll, no websockets, no state on the server, and it works
unchanged when several dashboards watch the same DB.
HTTP API reference #
Everything the UI renders is available as JSON, so you can point your own tooling (a TV wallboard, a spreadsheet, an alerting script) at the same endpoints.
| Endpoint | Returns |
|---|---|
GET /api/topology | Agents from config: name, provider/model, tools, handoffs, guardrail and eval refs, first line of instructions. |
GET /api/sessions?after_id=0&limit=100 | Session summaries (agent, step count, outcome, timestamps) for sessions with activity past after_id, newest first, plus the current cursor. |
GET /api/sessions/{id}/steps | The ordered step transcript for one session with parsed payloads. |
GET /api/stats | Chain verification result, SARR scorecard, step counts by type, tool-call counts, per-agent volume, top handover reasons, and steps per day. |
Security model #
- Self-hosted, like everything else. The dashboard is part of the package and runs inside your perimeter. No telemetry, no external assets, no egress.
- Read-only DB handle. SQLite is opened with
mode=ro; the viewer cannot create, modify, or delete audit rows. If the DB file does not exist yet, the dashboard shows empty states rather than creating it. - Localhost by default. It binds to
127.0.0.1. There is intentionally no built-in auth in this first version: if you expose it beyond localhost with--host, put your standard reverse proxy with SSO in front, the same way you publish any internal tool. - Payloads are visible. Transcripts show real customer messages, because that is the point of an ops view. Treat dashboard access like audit-log access.
Teams & scale #
The dashboard reads whatever audit DB you point it at. Replicas that share one audit path (or a WORM-backed mount) are all visible in a single dashboard; several team members can each run their own viewer against the same file, since readers take no locks that block the writer. The aggregate queries are one pass over the audit table, comfortable well past hundreds of thousands of rows; when your log outgrows a single SQLite file, the same four endpoints are the contract to reimplement over your own store.